Errores de la API
Un solo formato para todos los errores. Decide por `code`, que es estable; el `message` es para personas y puede cambiar.
{
"error": {
"code": "insufficient_scope",
"message": "Esta clave no tiene el scope passengers:read",
"request_id": "req_9f8e7d6c5b4a39281706",
"details": {
"required_scope": "passengers:read"
}
}
}| Campo | Qué es |
|---|---|
error.code | Código estable, de la tabla de abajo. Forma parte del contrato. |
error.message | Frase en castellano para una persona. No la interpretes: puede cambiar sin aviso. |
error.request_id | El mismo valor que la cabecera X-WAT-Request-Id. Guárdalo: es lo que identifica la petición en el servidor. |
error.details | Opcional. Claves habituales: param, field, allowed, required_scope, retry_after. |
Los mensajes nunca llevan SQL, trazas, nombres de tabla ni secretos. Lo que no es un error previsto sale como internal y el detalle se queda en el servidor, ligado al request_id.
Códigos
| HTTP | code | Cuándo |
|---|---|---|
| 400 | invalid_cursor | El cursor está manipulado, truncado o es de otra lista. |
| 400 | validation_error | Un parámetro o campo no es válido. details.param o details.field dice cuál; details.allowed, los valores admitidos. |
| 401 | expired_key | La clave ha caducado. |
| 401 | invalid_key | La clave no tiene la forma esperada o no existe. |
| 401 | revoked_key | La clave fue revocada (rotación o baja). |
| 401 | unauthorized | Falta Authorization: Bearer. |
| 401 | wrong_environment | Clave de test contra live o al revés. |
| 403 | api_disabled | La API no está activada para la organización, o el trozo que pides (por ejemplo los webhooks) no lo está. |
| 403 | forbidden | Reservado. Hoy ningún endpoint de v1 lo devuelve: los 403 reales son insufficient_scope, api_disabled e integration_disabled. |
| 403 | insufficient_scope | Falta un scope. details.required_scope dice cuál. |
| 403 | integration_disabled | La integración está desactivada o revocada. |
| 404 | not_found | No existe o no es de tu organización (misma respuesta en los dos casos). |
| 405 | method_not_allowed | El método HTTP no existe en esa ruta. Ver la nota de abajo: este 405 lo contesta el servidor antes que la API. |
| 409 | conflict | El estado actual no lo permite (p. ej. un external_id ya usado por otro recurso). |
| 409 | idempotency_conflict | La misma Idempotency-Key se usó antes con otro cuerpo. |
| 409 | idempotency_in_progress | Una petición con esa Idempotency-Key sigue en curso. Caduca a los 5 minutos: si el proceso que la tenía murió, el siguiente intento se ejecuta. |
| 413 | payload_too_large | El cuerpo supera 256 KB medidos en bytes UTF-8 (no en caracteres). |
| 415 | unsupported_media_type | El cuerpo no es application/json, o va sin Content-Type. |
| 429 | rate_limited | Demasiadas peticiones. Espera Retry-After segundos. |
| 500 | internal | Error interno. El detalle queda en el servidor con el request_id. |
| 501 | not_implemented | Reservado para operaciones anunciadas y aún no disponibles. Hoy ningún endpoint de v1 lo devuelve. |
Cómo tratarlos
| Si recibes | Haz |
|---|---|
| 401 | No reintentes con la misma clave. Revisa la clave, el entorno y si ha sido rotada. |
| 403 | No reintentes. Pide el scope o la activación que falta. |
| 404 | El id no es tuyo o ya no existe. No hay diferencia entre los dos casos. |
| 400 / 409 / 413 / 415 | Corrige la petición. Reintentar sin cambiarla dará lo mismo. |
| 429 | Espera Retry-After segundos y reintenta con espera exponencial. Ver Límites. |
| 500 | Reintenta con espera exponencial (un puñado de veces). Si persiste, escribe a soporte con el request_id. |
Un método que la ruta no tiene —por ejemplo DELETE /v1/bookings— lo rechaza el servidor antes de llegar a la API: responde 405 con la cabecera Allow, pero sin el sobre { "error": … } y sin X-WAT-Request-Id. Es el único caso en toda la v1 en que un error no lleva el formato de arriba, y por eso conviene decidir también por el código HTTP y no solo por error.code. Todo lo demás, incluidos los 404, va con el sobre completo y su request_id.
Ejemplo de validación con los valores admitidos:
{
"error": {
"code": "validation_error",
"message": "status no válido",
"request_id": "req_9f8e7d6c5b4a39281706",
"details": {
"param": "status",
"allowed": [
"requested",
"unconfirmed",
"confirmed",
"en_route",
"in_progress",
"completed",
"cancelled",
"no_show",
"rejected"
]
}
}
}