Recetas de la API
Cinco problemas de integración habituales y cómo resolverlos con curl.
Todas suponen que ya tienes un JWT en $JWT
(Autenticación).
1. Recorrer todas las páginas de un listado
Problema: un listado devuelve como máximo 100 elementos por petición y necesitas todos.
Solución: pide per_page=100 y avanza page mientras
meta.pagination.has_next_page sea true. No calcules el número de páginas por tu cuenta ni sigas
hasta recibir data vacío: una página fuera de rango no es un error.
page=1
while :; do
resp=$(curl -s "https://limbo.lefebvre.es/api/v1/assets?page=$page&per_page=100" \
-H "Authorization: Bearer $JWT")
echo "$resp" | jq -r '.data[].id'
[ "$(echo "$resp" | jq '.meta.pagination.has_next_page')" = "true" ] || break
page=$((page + 1))
done
Vigila:
- Si alguien sube o borra assets mientras recorres, un elemento puede aparecer dos veces o saltarse. Deduplica por
id. per_pagefuera de 1–100 es un 422;limitya no existe y también da 422.- Cada página cuenta como una petición del límite de 600 por minuto (receta 3).
2. Reintentar una subida sin duplicar
Problema: la conexión se corta tras enviar el fichero y no sabes si el asset llegó a crearse.
Solución: genera una Idempotency-Key (un UUID) antes de la primera petición, guárdala junto a la
tarea y reenvía exactamente la misma petición con la misma clave. Si la primera llegó a completarse recibes su respuesta guardada
(201 con Idempotent-Replayed: true) y no se crea un segundo asset.
KEY=$(uuidgen)
# Primer intento (se corta la conexión) y reintento: misma clave, mismo fichero
for attempt in 1 2 3; do
curl -i --fail-with-body -X POST https://limbo.lefebvre.es/api/v1/assets \
-H "Authorization: Bearer $JWT" \
-H "Idempotency-Key: $KEY" \
-F "file=@portada.jpg" -F "tags=portada" && break
sleep $((attempt * 2))
done
# HTTP/1.1 201 Created
# Idempotent-Replayed: true (solo si es la respuesta guardada del primer intento)
Vigila:
- La comparación del cuerpo es byte a byte. En multipart cuentan los campos, el nombre del fichero y el hash de su contenido. Un JSON re-serializado con otro orden de claves o espaciado se considera otro cuerpo: 422. Reenvía los bytes guardados, no vuelvas a construir la petición.
- 409: la primera petición sigue en curso. Espera unos segundos y reintenta con la misma clave.
- Una petición que falló (4xx o 5xx) no se guarda: puedes corregirla y reutilizar la clave. Para otra subida, genera una clave nueva.
- La respuesta guardada dura 24 horas.
3. Esperar ante un 429
Problema: una importación masiva supera las 600 peticiones por minuto del portal y recibes 429.
Solución: espera lo que indica Retry-After (segundos, de 1 a 60) y repite la misma petición.
No uses un tiempo fijo: la ventana es deslizante y la espera real suele ser menor que un minuto.
while :; do
code=$(curl -s -o body.json -D headers.txt -w '%{http_code}' \
"https://limbo.lefebvre.es/api/v1/assets?per_page=100" \
-H "Authorization: Bearer $JWT")
[ "$code" != "429" ] && break
wait=$(grep -i '^retry-after:' headers.txt | tr -dc '0-9')
sleep "${wait:-1}"
done
Vigila:
- Mira
X-RateLimit-Remainingen las respuestas correctas para frenar antes de llegar al 429. X-RateLimit-Resetes un timestamp Unix: si te quedan peticiones equivale a «ahora».- Un reintento tras un 429 en un
POSTde creación debe llevarIdempotency-Key(receta 2). - La petición de token tiene su propio límite (120 por minuto e IP): pide un JWT y reutilízalo durante sus 60 minutos.
4. Leer assets de otro portal
Problema: tu portal necesita mostrar imágenes que pertenecen a otro portal (por ejemplo, atenea).
Solución: un administrador concede la lectura por CLI y después tu portal filtra con
include_portals. Comprueba primero qué tienes concedido.
# 1. Administración (una vez, en el servidor de Limbo)
php bin/console limbo:grant-cross-portal-access mi-portal atenea
# 2. Qué portales puedo leer
curl https://limbo.lefebvre.es/api/v1/portals/me/cross-portal-access \
-H "Authorization: Bearer $JWT"
# 3. Listar solo los assets de atenea
curl "https://limbo.lefebvre.es/api/v1/assets?include_portals=atenea&per_page=50" \
-H "Authorization: Bearer $JWT"
Vigila:
include_portals=ateneadevuelve solo los assets de esos portales, no los tuyos. Para ambos, haz dos peticiones.- 403 en el listado: tu portal no tiene concedido ese slug. 400: lo combinaste con un filtro de carpeta.
- Es acceso de lectura.
PATCHyDELETEsobre un asset ajeno dan 403write_access_denied. - Un asset que no puedes leer da 404, igual que uno que no existe: no es un fallo de la ruta.
- Los tags son del portal: no se ven los de otro, ni con
include_portals.
5. Reemplazar el fichero de un asset conservando su id
Problema: la imagen de un asset cambió y quieres sustituirla sin perder el id con el que la referencian tus contenidos.
Solución: PATCH /api/v1/assets/{id} con el campo file (URL, base64 o data URI) en JSON.
El asset conserva su id, carpeta, tags y metadatos; el nombre de fichero pasa a ser el del nuevo, y la respuesta trae las URLs nuevas. Para fijar el nombre, envía también name en el mismo PATCH.
ID=3f1c9a52-7d0e-4b8a-9c11-0a52e6d4b7f1
# 1. Reemplazar
curl -X PATCH https://limbo.lefebvre.es/api/v1/assets/$ID \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{"file": "https://cdn.ejemplo.es/portada-v2.jpg"}'
# 2. Volver a generar las variantes que usabas
curl -X POST https://limbo.lefebvre.es/api/v1/assets/$ID/variants \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{"async": false, "variants": [{"name": "card", "width": 400, "height": 300, "format": "webp"}]}'
Vigila:
- Las variantes se borran al reemplazar: vuelve a generarlas (paso 2) y no sirvas sus URLs antiguas.
- Lo que se conserva es el
id, no la URL de descarga: el nombre del fichero nuevo puede cambiarla. Tras reemplazar, lee la URL de la respuesta (o vuelve a pedir el asset) en lugar de guardarla. - El fichero nuevo pasa la misma validación que una subida: formato no permitido o fichero inválido es 422, y una URL a una IP privada se rechaza.
- Un asset borrado da 404; uno de otro portal, 403
write_access_denied. - Usa JSON, no multipart: según el runtime, un
PATCHmultipart puede no llegar parseado.
Los campos de cada respuesta están en la referencia OpenAPI.