🔐 Autenticación
Cambia las credenciales de una API key por un JWT de corta duración y úsalo como Bearer.
Credenciales
Cada API key tiene dos claves: la pública (pk_…), que identifica la key y puede viajar en el
navegador, y la privada (sk_…), que solo existe en tu servidor. Una API key pertenece a un portal y
lleva una lista de scopes. La clave pública del portal ya no sirve en la v1:
usa siempre la pk_ de una API key.
El sk_ nunca va en el navegador
Un sk_ enviado desde un navegador se rechaza con 403 sin comprobarlo. Si llegó a verse en uno, considéralo expuesto
y pide otra key.
Pedir el token
| Método | Ruta | Scope | Qué hace |
|---|---|---|---|
| POST | /api/v1/auth/token | Ninguno (público) | Devuelve un JWT con los scopes de la key o de un subconjunto que pidas. 120 peticiones por minuto e IP |
| Campo del cuerpo JSON | Obligatorio | Descripción |
|---|---|---|
public_key | Sí | La pk_ de la API key |
api_key | No | El sk_ de la misma key. Solo desde servidor; hace falta para portal:admin |
scopes | No | Subconjunto de los scopes de la key. Sin él, el token lleva todos menos portal:admin |
El Content-Type debe ser application/json. Desde tu servidor:
curl -X POST https://limbo.lefebvre.es/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{
"public_key": "pk_tu_clave_publica",
"api_key": "sk_...",
"scopes": ["asset:read", "asset:write"]
}'
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"scopes": ["asset:read", "asset:write"],
"portal": {
"id": "7b0c2f1e-5a3d-4c9b-8e21-6d4f90a1c3b2",
"name": "Mi portal",
"public_key": "pk_tu_clave_publica"
},
"policies": {
"format_policy": { "allowed_output_formats": ["webp", "jpg", "png"] },
"presets_aspect": [],
"presets_size": [],
"allow_arbitrary": true,
"cross_access_policy": "own"
}
}
}
La respuesta lleva Cache-Control: no-store; no la caches. portal.public_key es la de la key, no la del portal.
expires_in son 3600 s (60 minutos) salvo que la API key tenga otra duración (entre 60 y 86400 s).
Pedirlo desde tu backend
$ch = curl_init('https://limbo.lefebvre.es/api/v1/auth/token');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'public_key' => getenv('LIMBO_PUBLIC_KEY'),
'api_key' => getenv('LIMBO_SECRET_KEY'),
]),
CURLOPT_RETURNTRANSFER => true,
]);
$body = json_decode(curl_exec($ch), true);
$jwt = $body['data']['token'] ?? throw new RuntimeException($body['detail'] ?? 'Limbo token error');
const res = await fetch('https://limbo.lefebvre.es/api/v1/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
public_key: process.env.LIMBO_PUBLIC_KEY,
api_key: process.env.LIMBO_SECRET_KEY,
}),
});
const body = await res.json();
if (!res.ok) throw new Error(body.detail);
const jwt = body.data.token;
Para el navegador, tu backend devuelve solo el JWT y el componente lo pide con tokenProvider
(configuración del componente).
Usar el token
Envíalo en cada petición. Cuando caduque (401), pide otro:
curl https://limbo.lefebvre.es/api/v1/assets \
-H "Authorization: Bearer $JWT"
Scopes
El token solo puede lo que dicen sus scopes; sin el scope necesario la respuesta es 403.
| Scope | Lo piden |
|---|---|
asset:read | Listar y leer assets, tags, portals/me/cross-portal-access; también sirve para listar carpetas |
asset:write | Subir, editar y restaurar assets; variants/from-source |
asset:delete | Borrar assets |
variant:read | Listar variantes de un asset |
variant:write | Generar, subir y editar variantes |
variant:delete | Borrar variantes |
folder:read | Listar carpetas y sus assets |
folder:write | Crear, editar y borrar carpetas |
portal:read | portals/me, listar presets, métricas |
portal:admin | Crear, editar y borrar presets; external/portals/{slug}/refresh |
ai:read / ai:generate | Servicios y parámetros de IA / generar con IA (imagen y vídeo) |
stock:search / stock:download | Buscar en bancos de imágenes / descargar un resultado |
external:read | Leer contenido de portales externos |
portal:admin
Es el único scope que no se concede por defecto. Para obtenerlo pídelo en scopes y envía el
api_key (sk_) de esa key; si falta, 403 portal_admin_requires_api_key. Pedir un scope que la key no
tiene es un 422.
Navegador, Origin y sk_
- Si la petición lleva cabecera
Originy envíasapi_key: 403sk_not_allowed_from_browser, sin mirar la clave. - Si el
Originno está en losallowedOriginsdel portal: 403origin_not_allowed. - Una petición sin
Origin(tu servidor) no se comprueba contraallowedOrigins.
Errores propios del token
El código de máquina es el prefijo de detail.
| Estado | detail empieza por | Causa |
|---|---|---|
| 400 | Invalid JSON payload. | JSON inválido o cuya raíz no es un objeto |
| 401 | invalid_credentials | pk_ desconocida (incluida la del portal), key revocada, portal inactivo o api_key incorrecta; no se distinguen entre sí |
| 403 | origin_not_allowed, sk_not_allowed_from_browser, portal_admin_requires_api_key | Ver las secciones anteriores |
| 415 | Supported content type | Falta Content-Type: application/json |
| 422 | The request contains invalid parameters. | Cuerpo inválido, o scopes que la key no tiene (con errors[]) |
| 429 | Rate limit exceeded | Más de 120 peticiones por minuto desde la misma IP; espera Retry-After segundos |
Los schemas de las respuestas están en la referencia OpenAPI. Siguiente paso: el inicio rápido.