MétodoRutaScopeQué hace
GET/api/v1/assetsasset:readLista paginada con filtros
POST/api/v1/assetsasset:writeSube un fichero (201 + Location)
GET/api/v1/assets/{id}asset:readDetalle con URLs firmadas
GET/api/v1/assets/f/{folderSlug}/{filename}asset:readDetalle por carpeta y nombre de fichero
PATCH/api/v1/assets/{id}asset:writeCambia metadatos, tags, carpeta o reemplaza el fichero
DELETE/api/v1/assets/{id}asset:deleteBorrado lógico
POST/api/v1/assets/{id}/restoreasset:writeDeshace un borrado
GET/api/v1/foldersfolder:read o asset:readLista carpetas
POST/api/v1/foldersfolder:writeCrea una carpeta (201)
GET/api/v1/folders/{id}folder:read o asset:readDetalle de carpeta
PATCH/api/v1/folders/{id}folder:writeEdita una carpeta (no hay PUT)
DELETE/api/v1/folders/{id}folder:writeBorra una carpeta (204)
GET/api/v1/folders/{id}/assetsfolder:read o asset:readAssets de una carpeta, paginados
GET/api/v1/tagsasset:readTags del portal con su uso

El portal es siempre el del token: ninguna ruta acepta portal_id. Los ejemplos suponen $JWT con un token válido (Autenticación).

Listar assets

Orden fijo: fecha de creación descendente. Paginación con page y per_page (máximo 100); limit no existe y da 422. Los filtros usan snake_case y se combinan con AND.

ParámetroSignificado
statusactive (por defecto), processing, error o deleted
name, qTexto en el nombre del fichero / búsqueda libre (máx. 255)
tagsCSV de tags: el asset debe tenerlos todos (AND). Para «cualquiera de estos» haz una petición por tag
asset_typeimage, video, document o audio
acceptTipos MIME o categorías, como la cabecera Accept
folder, folders_include, folders_exclude, include_no_folderFiltros por carpeta (slug o id; CSV en folders_*)
uploaded_by, date_from, date_toAutor y rango de fechas (YYYY-MM-DD)
include_portalsCSV de slugs de otros portales cuyos assets quieres leer (ver más abajo)
url_expiresSegundos de validez de las URLs firmadas. Por defecto 0: URLs permanentes (permanent: true, expires_at: null)
curl
curl "https://limbo.lefebvre.es/api/v1/assets?tags=portada,otono&asset_type=image&page=1&per_page=20" \
  -H "Authorization: Bearer $JWT"

Otros portales. include_portals=atenea devuelve solo los assets de esos portales (no los del tuyo) y exige que tu portal tenga esos slugs en crossPortalReadAccess; se concede por CLI (limbo:grant-cross-portal-access). Sin concesión es 403 y no se combina con filtros de carpeta (400). Es acceso de lectura: PATCH y DELETE sobre un asset ajeno dan 403 write_access_denied, y un asset que no puedes leer es un 404 indistinguible de uno que no existe.

Subir un asset

Tres formas de enviar el fichero. En las tres, folder (slug; se crea si no existe) y tags son opcionales, y la respuesta es 201 con la cabecera Location del nuevo asset. Acepta Idempotency-Key.

curl
# 1. multipart/form-data
curl -X POST https://limbo.lefebvre.es/api/v1/assets \
  -H "Authorization: Bearer $JWT" \
  -F "file=@portada.jpg" -F "folder=campana-otono" -F "tags=portada,otono"

# 2. JSON con data URI base64
curl -X POST https://limbo.lefebvre.es/api/v1/assets \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"file": "data:image/png;base64,iVBORw0KGgo...", "filename": "logo.png", "tags": ["logo"]}'

# 3. JSON con una URL http(s) que descarga Limbo
curl -X POST https://limbo.lefebvre.es/api/v1/assets \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"file": "https://cdn.ejemplo.es/foto.webp", "folder": "campana-otono"}'
  • Tipos aceptados: solo imágenes JPEG, PNG, WebP y GIF. Un fichero ausente o inválido es 422.
  • Tags: CSV o lista; se pasan a minúsculas, solo a-z, 0-9, -, _ y espacio, hasta 40 caracteres y 100 por asset.
  • Otros campos: store_original (booleano, por defecto true), uploaded_by, y en JSON filename / mime_type para describir los bytes decodificados o descargados.
  • Descarga por URL: pasa por un proxy protegido. Se rechazan IPs privadas, cada redirección se revalida y hay 10 s y 200 MB como máximo; el error devuelve un texto fijo.
  • Cuerpo: multipart/form-data o application/json; otro Content-Type es 415.

Leer un asset

curl
curl "https://limbo.lefebvre.es/api/v1/assets/3f1c9a52-7d0e-4b8a-9c11-0a52e6d4b7f1?url_expires=7200" \
  -H "Authorization: Bearer $JWT"

curl https://limbo.lefebvre.es/api/v1/assets/f/campana-otono/portada.jpg \
  -H "Authorization: Bearer $JWT"

La respuesta lleva las URLs firmadas del fichero (url, url_original, de la forma /media/{slug}/{nombre}?a={token}), permanent, expires_at, tags y las variantes. Con url_expires mayor que 0 las URLs caducan pasado ese tiempo. include_variants=false e include_metadata=false a la vez dan la forma reducida. En /assets/f/…, una carpeta inexistente, una carpeta no visible para el token y un fichero inexistente responden el mismo 404. Un asset borrado también es 404.

Editar, reemplazar, borrar y restaurar

PATCH acepta name, folder (null lo saca de su carpeta), tags (sustituye la lista), metadata (se fusiona con la actual) y file. Con null, name, tags y metadata se ignoran. Todo se valida antes de cambiar nada: un campo inválido no deja el asset a medias; un cuerpo vacío es 400.

curl
ID=3f1c9a52-7d0e-4b8a-9c11-0a52e6d4b7f1

# Metadatos y tags
curl -X PATCH https://limbo.lefebvre.es/api/v1/assets/$ID \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"name": "portada-final.jpg", "tags": ["portada"], "metadata": {"autor": "Marketing"}}'

# Reemplazar el fichero (mismo id; se borran las variantes anteriores)
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"}'

# Borrado lógico y restauración
curl -X DELETE https://limbo.lefebvre.es/api/v1/assets/$ID -H "Authorization: Bearer $JWT"
curl -X POST   https://limbo.lefebvre.es/api/v1/assets/$ID/restore -H "Authorization: Bearer $JWT"
  • El reemplazo de fichero admite las mismas tres formas que la subida. Para el canal multipart usa mejor JSON: según el runtime (PHP/FrankenPHP) un PATCH multipart puede no llegar parseado.
  • DELETE y restore responden {id, status}. Borrar un asset ya borrado, o restaurar uno que no lo está, es 409. Restaurar exige asset:write; asset:delete no basta.
  • El borrado es lógico: no toca ficheros ni variantes hasta que la purga (app:purge-deleted-assets) los elimine.

Carpetas

Cuerpos JSON. name (1–100) es obligatorio al crear; slug (a-z0-9_-) se genera del nombre si falta. Opcionales: description (500), visibility, sort_order (0–1 000 000) y metadata (10 KB). Una clave desconocida, o id / portal_id, es 422.

curl
curl -X POST https://limbo.lefebvre.es/api/v1/folders \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"name": "Campaña otoño", "visibility": "public"}'

curl -X PATCH https://limbo.lefebvre.es/api/v1/folders/{folder_id} \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"description": "Material de la campaña"}'

# Con assets dentro: 409 folder_not_empty salvo force=true (los assets quedan sin carpeta)
curl -X DELETE "https://limbo.lefebvre.es/api/v1/folders/{folder_id}?force=true" \
  -H "Authorization: Bearer $JWT"

curl "https://limbo.lefebvre.es/api/v1/folders/{folder_id}/assets?page=1&per_page=50" \
  -H "Authorization: Bearer $JWT"
  • Visibilidad: public siempre; internal y private solo si el token lleva folder:internal / folder:private, tanto para verlas como para asignarlas. Una carpeta que el token no puede ver es un 404.
  • Un slug o nombre repetido es 409 folder_conflict. force solo admite true o false.
  • La respuesta incluye assets_count. El listado se pagina con page / per_page y admite visibility.
  • POST admite Idempotency-Key.

Tags

curl
curl "https://limbo.lefebvre.es/api/v1/tags?search=port&page=1&per_page=20" \
  -H "Authorization: Bearer $JWT"

{ "success": true,
  "data": [ { "name": "portada", "usage_count": 12 } ],
  "meta": { "pagination": { "current_page": 1, "total_pages": 1, "total_items": 1, "items_per_page": 20, "has_next_page": false, "has_prev_page": false } } }

Orden por usage_count descendente y, en empate, por nombre. search filtra por prefijo (máx. 40 caracteres). Los tags son del portal: no se ven los de otro portal ni siquiera con include_portals. limit da 422.

Errores propios de esta página

EstadoCausa
400Cuerpo JSON inválido, PATCH sin ningún campo, o include_portals combinado con filtros de carpeta
403Falta el scope, falta acceso cross-portal, o write_access_denied (asset legible pero de otro portal)
404Asset o carpeta inexistente, borrado, ilegible para el portal o de visibilidad no permitida (sin distinguir)
409Borrar un asset ya borrado, restaurar uno activo, folder_conflict, folder_not_empty, o Idempotency-Key en curso
415Content-Type no soportado
422Campo inválido (con errors[]), fichero ausente o no válido, limit, o Idempotency-Key con otro cuerpo

Formato de los errores y límites generales en Conceptos. Schemas completos en la referencia OpenAPI. Siguiente: Variantes y entrega.