Errores y respuestas
Cada endpoint de la API pública devuelve formas de respuesta predecibles. Los errores incluyen en el cuerpo un código estable legible por máquina para que puedas ramificar el código sin tener que parsear texto en inglés.
Envoltorio de respuesta
Las respuestas correctas devuelven el recurso (o una lista de recursos) directamente. No hay envoltorio en el nivel superior: los clientes pueden deserializar directamente en un modelo tipado:
GET /api/public/v1/org
{
"id": "e188fc87-…",
"name": "Acme Corp",
"plan": "growth",
…
}Las respuestas de error usan una pequeña forma uniforme con un código error como único campo obligatorio. Los campos opcionales aportan contexto que ayuda a corregir la llamada:
{
"error": "insufficient_scope",
"required": "assignments:write",
"present": ["assignments:read", "users:read"]
}Referencia de códigos de estado
| Estado | Cuándo lo verás | Qué hacer |
|---|---|---|
200 | Solicitud correcta. El cuerpo contiene el recurso o un mensaje de confirmación. | Procesa el cuerpo. |
400 | Solicitud mal formada: campo obligatorio ausente, valor de enum inválido, cuerpo demasiado grande o JSON no parseable. | Corrige la solicitud del cliente: no reintentes con el mismo payload. |
401 | Token ausente, mal formado, revocado o expirado. | Verifica el header Authorization. Rota el token si ha sido revocado. |
403 | El token es válido pero le falta el scope requerido. | Añade el scope desde la UI de administración; la respuesta lleva el nombre exacto del scope. |
404 | El recurso no existe o pertenece a otra organización. | Vuelve a obtener la colección padre. No persigas IDs obsoletos entre organizaciones. |
409 | Conflicto, por ejemplo, un email ya en uso. | Trátalo como éxito si tu operación es idempotente. |
413 | El cuerpo de la solicitud supera el tope por endpoint. Lo devuelve Kestrel ante un desbordamiento duro; el endpoint de SARIF también lo devuelve como 400 body_too_large cuando el Content-Length declarado supera 10 MB. | Recorta el payload. Para SARIF, ingiere de forma incremental los hallazgos de una herramienta a la vez. |
429 | Se ha alcanzado el límite de tasa en esta API key. | Respeta Retry-After; consulta Límites de tasa. |
5xx | Problema en el lado del servidor. | Reintenta con backoff exponencial. Captura el header X-Request-Id de la respuesta y cítalo en cualquier ticket de soporte. |
Códigos de error
El campo error es estable entre versiones: ramifica el código de cliente sobre él en lugar del estado HTTP, ya que un mismo estado puede llevar códigos distintos:
| Código | Estado | Significado |
|---|---|---|
unauthorized | 401 | No hay un bearer token utilizable en la solicitud. |
insufficient_scope | 403 | El token está bien, pero falta scope. La respuesta incluye required y present. |
rate_limited | 429 | Tope excedido. Consulta el header Retry-After. |
user_not_found | 404 | El ID de usuario no existe o no está en tu organización. |
team_not_found | 404 | El ID de equipo no existe o no está en tu organización. |
assignment_not_found | 404 | El ID de asignación no existe o no está en tu organización. |
empty_body | 400 | Se envió POST o PATCH sin cuerpo donde sí es obligatorio. |
body_too_large | 400 / 413 | El cuerpo de la solicitud superó el tope del endpoint. SARIF lo emite como 400 con maxBytes en el cuerpo cuando el Content-Length declarado es demasiado grande; Kestrel emite un 413 escueto cuando el cuerpo se desborda realmente. |
IDs de solicitud
Cada respuesta lleva un header X-Request-Id (UUID v4). Captúralo en el cliente e inclúyelo en las solicitudes de soporte: nos permite recuperar la traza exacta del servidor para la llamada fallida. Para las respuestas 5xx, el ID de solicitud es la vía más rápida para el diagnóstico.
Errores de validación
Los fallos de validación en cuerpos POST y PATCH devuelven 400 con un error plano que describe la primera regla que falla. La superficie de validación multicampo no forma parte de v1: corrige el primer campo reportado y reintenta. Futuras versiones podrán añadir detalle por campo; el contrato del error de nivel superior se mantendrá.
Ramificar según el error: ejemplo
const res = await fetch(url, { headers });
if (res.ok) return res.json();
const body = await res.json().catch(() => ({}));
switch (body.error) {
case 'rate_limited':
return retryWithBackoff(url);
case 'insufficient_scope':
throw new Error(`API key missing scope: ${body.required}`);
case 'unauthorized':
throw new Error('API key revoked or invalid — rotate the secret');
default:
throw new Error(`API error ${res.status}: ${body.error || 'unknown'}`);
}