Errores + límites de tasa
Un único envoltorio de error en todos los endpoints. Códigos de estado HTTP estándar + una cadena code estructurada para la coincidencia programática. Cabeceras de límite de tasa en cada respuesta para que pueda regular su ritmo antes de alcanzar un 429.
Envoltorio de error
{
"detail": "Human-readable summary.",
"code": "billing.subscription_past_due",
"field_errors": {
"scheduled_for": ["Must be a future ISO 8601 timestamp."]
}
} detail: siempre presente, seguro para mostrar a personas (está traducido cuando el idioma está disponible).code: presente para estados de error distinguibles. Estable entre versiones de la API; haga coincidir con este para la lógica de reintento.field_errors: presente solo en los fallos de validación 400. Mapa de nombre-de-campo → lista de mensajes.
Matriz de códigos de estado
| Estado | Clase | ¿Reintentar? | Causas comunes |
|---|---|---|---|
| 400 | Validación | No | Falta un campo obligatorio, formato incorrecto, violación de regla de negocio |
| 401 | Autenticación | No | Credencial ausente / incorrecta / revocada |
| 402 | Facturación | No | Suscripción vencida, límite del plan gratuito alcanzado |
| 403 | Permiso | No | La credencial carece del ámbito, o el recurso está en otra organización |
| 404 | No encontrado | No | El recurso no existe, o no es visible para su credencial |
| 409 | Conflicto | A veces | Discrepancia en la reutilización de Idempotency-Key, violación de máquina de estados |
| 422 | No procesable | No | Combinación semánticamente no válida de campos válidos |
| 429 | Límite de tasa | Sí, con backoff | Estrangulamiento por credencial o por organización alcanzado |
| 500 | Error de servidor | Sí, con backoff | Inesperado: abra un ticket si persiste |
| 502 / 503 / 504 | Transitorio | Sí, con backoff | Upstream / despliegue / carga |
Códigos de error notables
| Código | Estado | Qué significa |
|---|---|---|
billing.subscription_past_due | 402 | La suscripción de Stripe de la organización está vencida. Dirija al administrador a /admin/billing. |
billing.free_plan_minutes_exhausted | 402 | El nivel Gratuito / Piloto ha usado sus 5 inspecciones. |
billing.signature_pack_exhausted | 402 | Créditos del pack de firmas agotados; pague por firma o compre otro pack. |
permission_denied | 403 | La credencial carece del ámbito. Consulte field_errors.required_scope. |
idempotency.key_mismatch | 409 | Misma clave, cuerpo distinto. Use una clave nueva. |
session.already_ended | 409 | Llamar a end en una sesión no abierta (normalmente seguro: end es idempotente, esto es para los casos límite). |
kyb.not_verified | 403 | Se solicitó una firma QES en una organización que no ha superado el KYB. |
rate_limited | 429 | Estrangulamiento por credencial (60 rpm) o por organización (600 rpm) alcanzado. |
Cabeceras de límite de tasa
Cada respuesta, con éxito o error, lleva el estado del estrangulamiento:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1716461235 X-RateLimit-Limit: el tope que se aplica a esta petición.X-RateLimit-Remaining: cuántas llamadas quedan en la ventana actual.X-RateLimit-Reset: marca de tiempo Unix en la que se reiniciaRemaining.
En un 429, también se establece Retry-After (en segundos) según RFC 6585.
Ámbitos de estrangulamiento
| Ámbito | Límite | Ventana |
|---|---|---|
| Por credencial | 60 | 1 minuto |
| Por organización (en todas las credenciales) | 600 | 1 minuto |
Admite ráfagas durante el primer segundo de un minuto. Alcance el tope y las peticiones siguientes dentro de la ventana devuelven 429.
Estrategia de reintento
Backoff exponencial con jitter para 429 / 5xx; no reintente los 4xx (salvo 408, 425, 429):
async function withRetry<T>(fn: () => Promise<T>, max = 5): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < max; i++) {
try {
return await fn();
} catch (err: any) {
const status = err?.status;
if (status >= 400 && status < 500 && ![408, 425, 429].includes(status)) {
throw err; // not retryable
}
const base = status === 429 ? (err.retryAfterMs ?? 1000) : 250 * 2 ** i;
const jitter = Math.random() * base * 0.2;
await new Promise((r) => setTimeout(r, base + jitter));
lastErr = err;
}
}
throw lastErr;
} Los SDK implementan esto internamente en 429 + 5xx; si prefiere gestionar los reintentos usted mismo, establezca retries: 0 en las opciones del SDK.
Correlación de errores
Cada respuesta lleva una cabecera X-Request-Id (UUID). Cítela en cualquier ticket de soporte: podemos extraer la traza completa de la petición solo con ese id.
Qué sigue
- Autenticación — rotación de credenciales si está viendo errores 401
- Paginación + idempotencia — los patrones que interactúan con los reintentos