🤖 IA, stock y contenido externo
Generación de imágenes y vídeo con IA, búsqueda y descarga en bancos de imágenes, y lectura de portales WordPress externos. Todo se sirve a través de Atenea.
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| GET | /api/v1/ai/services | ai:read | Servicios de imagen disponibles |
| GET | /api/v1/ai/params | ai:read | Parámetros de uno o varios servicios |
| POST | /api/v1/ai/generate | ai:generate | Genera una imagen |
| GET | /api/v1/ai/jobs/{id} | ai:read | Estado de un trabajo de imagen |
| GET | /api/v1/ai/video/services | ai:read | Servicios de vídeo |
| GET | /api/v1/ai/video/params | ai:read | Parámetros de un servicio de vídeo |
| GET | /api/v1/ai/video/capabilities | ai:read | Capacidades de un servicio de vídeo |
| POST | /api/v1/ai/video/generate | ai:generate | Genera un vídeo |
| GET | /api/v1/ai/video/jobs/{id} | ai:read | Estado de un trabajo de vídeo |
| GET | /api/v1/stock/services | stock:search | Bancos de imágenes disponibles |
| GET | /api/v1/stock/search | stock:search | Busca imágenes (paginado) |
| POST | /api/v1/stock/download | stock:download | Descarga una imagen del banco |
| GET | /api/v1/external/portals | external:read | Portales externos disponibles |
| GET | /api/v1/external/portals/{slug} | external:read | Detalle de un portal externo |
| POST | /api/v1/external/portals/{slug}/refresh | portal:admin | Fuerza la actualización de su caché |
| GET | /api/v1/external/media | external:read | Medios de un portal externo |
| GET | /api/v1/external/articles | external:read | Artículos de uno o varios portales |
| GET | /api/v1/external/articles/{id} | external:read | Un artículo (id numérico) |
Los scopes ai:*, stock:* y external:read se conceden a la API key
(Autenticación). external:read es nuevo en v1 y no lo tiene ninguna key existente:
un administrador lo concede por CLI con php bin/console limbo:grant-scope <api-key-id> external:read.
Los textos de error de Atenea nunca llegan al cliente: recibes siempre el error normalizado de Limbo.
Imágenes con IA
curl
curl https://limbo.lefebvre.es/api/v1/ai/services -H "Authorization: Bearer $JWT"
curl "https://limbo.lefebvre.es/api/v1/ai/params?services=flux-pro,dalle3&form=true" -H "Authorization: Bearer $JWT"
curl -X POST https://limbo.lefebvre.es/api/v1/ai/generate \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-H "Idempotency-Key: 3f1c9a52-8d07-4c1e-9b0a-52d0f7a1e001" \
-d '{"service": "flux-pro", "prompt": "Oficina luminosa con plantas, estilo editorial", "aspect_ratio": "16:9"}'
# Éxito (recortado)
{ "success": true, "data": { "status": "completed", "images": [ "https://..." ] } }
# La espera se agotó: 202
{ "success": true, "data": { "job_id": "job_123", "status": "processing" } }
curl https://limbo.lefebvre.es/api/v1/ai/jobs/job_123 -H "Authorization: Bearer $JWT"
paramsaceptaservices(slugs separados por coma) yform(true|false|1|0).servicees un slug (minúsculas, dígitos,.,_y-) ypromptadmite hasta 4000 caracteres. El resto de campos son las opciones del modelo (escalares o listas de escalares, máximo 40 campos, nombres que empiezan por minúscula; letras, dígitos y_): las valida Atenea, y si las rechaza es un 422upstream_rejected.generatees síncrono. Limbo espera internamente al resultado (hasta 30 consultas, la primera tras 5 s) y lo devuelve endata. Si la espera se agota responde 202 conjob_idystatus: processing: consulta entoncesjobs/{id}hasta que termine.asyncyqueryno existen en v1 y dan 422.- Un
job_idsolo es visible para el portal que lo creó (si no, 404). Los trabajos creados con la API antigua no se ven en v1. - Usa
Idempotency-Keyengenerate: un reintento por timeout de red no vuelve a generar (ni a cobrar) la imagen.
Vídeo con IA
curl
curl "https://limbo.lefebvre.es/api/v1/ai/video/params?service=kling" -H "Authorization: Bearer $JWT"
curl "https://limbo.lefebvre.es/api/v1/ai/video/capabilities?service=kling" -H "Authorization: Bearer $JWT"
curl -X POST https://limbo.lefebvre.es/api/v1/ai/video/generate \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-d '{"service": "kling", "prompt": "Panorámica lenta sobre una oficina al amanecer", "image": "data:image/jpeg;base64,/9j/4AAQ..."}'
paramsycapabilitiesexigenservice; si falta o no es un slug, 422.- Los medios de entrada van como data URI en
image,image2,last_frame,videooreference_images(de 1 a 3). Qué combinaciones admite cada servicio sale decapabilities. - Misma mecánica que en imagen: Limbo espera (hasta 6 consultas de 5 s) y, si se agota, responde 202 con
job_idpara consultarvideo/jobs/{id}.asyncyquerydan 422.
Bancos de imágenes
curl
curl https://limbo.lefebvre.es/api/v1/stock/services -H "Authorization: Bearer $JWT"
curl "https://limbo.lefebvre.es/api/v1/stock/search?service=pexels&query=oficina+moderna&orientation=horizontal&page=1&per_page=20" \
-H "Authorization: Bearer $JWT"
curl -X POST https://limbo.lefebvre.es/api/v1/stock/download \
-H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
-H "Idempotency-Key: 9a7d1c40-52e3-4b6f-8a10-0c5e3d7b2f11" \
-d '{"service": "pexels", "image_id": "1234567", "size": "large", "filename": "oficina-moderna"}'
searches unGETconserviceyquery(de 5 a 200 caracteres) obligatorios. Filtros opcionales:orientation(horizontal|vertical|square),image_type(photo|illustration|vector),category,color,size,width_from,width_to,height_fromyheight_to. Pagina conpageyper_page(máximo 100);limitda 422.downloadexigeimage_id(el campoides un 422) y admitesize(original|large|medium|small) yfilename. Es idempotente conIdempotency-Key.
Contenido externo (WordPress)
curl
curl https://limbo.lefebvre.es/api/v1/external/portals -H "Authorization: Bearer $JWT"
curl "https://limbo.lefebvre.es/api/v1/external/media?portal=mi-portal&media_type=images&order=DESC&per_page=20" \
-H "Authorization: Bearer $JWT"
curl "https://limbo.lefebvre.es/api/v1/external/articles?portal=portal-a,portal-b&page=1&per_page=20" \
-H "Authorization: Bearer $JWT"
curl https://limbo.lefebvre.es/api/v1/external/articles/4521 -H "Authorization: Bearer $JWT"
- Un portal inactivo o desconocido es siempre 404.
mediafiltra pormedia_type(images|videos|all),search,order(ASC|DESC),post_typeycategory.articlesexigeportal: un slug o una lista de 1 a 10 separados por coma. Con varios portales,page × per_pageno puede pasar de 1000. El{id}del artículo son solo dígitos.refreshexigeportal:admin(noexternal:read) y no es idempotente.
Errores propios de esta página
| Estado | Causa |
|---|---|
| 202 | generate no terminó dentro de la espera interna: consulta jobs/{id} |
| 403 | Falta el scope de la ruta (ai:generate, stock:download, external:read…) |
| 404 | Trabajo inexistente o de otro portal; portal externo inactivo o desconocido; artículo inexistente |
| 422 | Parámetro inválido o desconocido (limit, async, query, id en descarga…), o Atenea rechazó la petición (upstream_rejected) |
| 502 / 504 | Atenea falló o no respondió a tiempo. Reintenta con la misma Idempotency-Key |
Formato general de errores en Conceptos; schemas completos en la referencia OpenAPI.