🖼️ Assets, carpetas y tags
Subir, listar, editar y borrar assets; organizarlos en carpetas y etiquetarlos. Todas las rutas cuelgan de /api/v1/.
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| GET | /api/v1/assets | asset:read | Lista paginada con filtros |
| POST | /api/v1/assets | asset:write | Sube un fichero (201 + Location) |
| GET | /api/v1/assets/{id} | asset:read | Detalle con URLs firmadas |
| GET | /api/v1/assets/f/{folderSlug}/{filename} | asset:read | Detalle por carpeta y nombre de fichero |
| PATCH | /api/v1/assets/{id} | asset:write | Cambia metadatos, tags, carpeta o reemplaza el fichero |
| DELETE | /api/v1/assets/{id} | asset:delete | Borrado lógico |
| POST | /api/v1/assets/{id}/restore | asset:write | Deshace un borrado |
| GET | /api/v1/folders | folder:read o asset:read | Lista carpetas |
| POST | /api/v1/folders | folder:write | Crea una carpeta (201) |
| GET | /api/v1/folders/{id} | folder:read o asset:read | Detalle de carpeta |
| PATCH | /api/v1/folders/{id} | folder:write | Edita una carpeta (no hay PUT) |
| DELETE | /api/v1/folders/{id} | folder:write | Borra una carpeta (204) |
| GET | /api/v1/folders/{id}/assets | folder:read o asset:read | Assets de una carpeta, paginados |
| GET | /api/v1/tags | asset:read | Tags 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ámetro | Significado |
|---|---|
status | active (por defecto), processing, error o deleted |
name, q | Texto en el nombre del fichero / búsqueda libre (máx. 255) |
tags | CSV de tags: el asset debe tenerlos todos (AND). Para «cualquiera de estos» haz una petición por tag |
asset_type | image, video, document o audio |
accept | Tipos MIME o categorías, como la cabecera Accept |
folder, folders_include, folders_exclude, include_no_folder | Filtros por carpeta (slug o id; CSV en folders_*) |
uploaded_by, date_from, date_to | Autor y rango de fechas (YYYY-MM-DD) |
include_portals | CSV de slugs de otros portales cuyos assets quieres leer (ver más abajo) |
url_expires | Segundos de validez de las URLs firmadas. Por defecto 0: URLs permanentes (permanent: true, expires_at: null) |
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.
# 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 defectotrue),uploaded_by, y en JSONfilename/mime_typepara 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-dataoapplication/json; otroContent-Typees 415.
Leer un asset
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.
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
PATCHmultipart puede no llegar parseado. DELETEy restore responden{id, status}. Borrar un asset ya borrado, o restaurar uno que no lo está, es 409. Restaurar exigeasset:write;asset:deleteno 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 -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:
publicsiempre;internalyprivatesolo si el token llevafolder: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.forcesolo admitetrueofalse. - La respuesta incluye
assets_count. El listado se pagina conpage/per_pagey admitevisibility. POSTadmiteIdempotency-Key.
Tags
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
| Estado | Causa |
|---|---|
| 400 | Cuerpo JSON inválido, PATCH sin ningún campo, o include_portals combinado con filtros de carpeta |
| 403 | Falta el scope, falta acceso cross-portal, o write_access_denied (asset legible pero de otro portal) |
| 404 | Asset o carpeta inexistente, borrado, ilegible para el portal o de visibilidad no permitida (sin distinguir) |
| 409 | Borrar un asset ya borrado, restaurar uno activo, folder_conflict, folder_not_empty, o Idempotency-Key en curso |
| 415 | Content-Type no soportado |
| 422 | Campo 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.