Docs/API y webhooks/Errores y respuestas

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

EstadoCuándo lo verásQué hacer
200Solicitud correcta. El cuerpo contiene el recurso o un mensaje de confirmación.Procesa el cuerpo.
400Solicitud 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.
401Token ausente, mal formado, revocado o expirado.Verifica el header Authorization. Rota el token si ha sido revocado.
403El 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.
404El recurso no existe o pertenece a otra organización.Vuelve a obtener la colección padre. No persigas IDs obsoletos entre organizaciones.
409Conflicto, por ejemplo, un email ya en uso.Trátalo como éxito si tu operación es idempotente.
413El 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.
429Se ha alcanzado el límite de tasa en esta API key.Respeta Retry-After; consulta Límites de tasa.
5xxProblema 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ódigoEstadoSignificado
unauthorized401No hay un bearer token utilizable en la solicitud.
insufficient_scope403El token está bien, pero falta scope. La respuesta incluye required y present.
rate_limited429Tope excedido. Consulta el header Retry-After.
user_not_found404El ID de usuario no existe o no está en tu organización.
team_not_found404El ID de equipo no existe o no está en tu organización.
assignment_not_found404El ID de asignación no existe o no está en tu organización.
empty_body400Se envió POST o PATCH sin cuerpo donde sí es obligatorio.
body_too_large400 / 413El 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'}`);
}