Sección 16
Errores de la API
Este es el contrato de error de /v1. No es el mismo vocabulario que el de su servidor de
herramientas: los seis valores que el agente interpreta están en la página 06 y
significan «qué hace el agente». Aquí se habla de transporte, y los dos vocabularios no se mezclan a
propósito — por eso el «no existe» de esta superficie se llama resource_not_found y no NOT_FOUND.
La forma, siempre igual
{
"error": {
"code": "insufficient_credits",
"message": "El saldo de créditos de su cuenta no cubre esta operación.",
"request_id": "req_22398948bdff3f425bdeb7da",
"doc_url": "https://asixto.com/docs/gateway/errors#insufficient_credits"
}
}Programe contra code. Es lo único estable: el estado HTTP significa cosas distintas según el cliente
que use, el texto se reescribe y el idioma se traduce. Renombrar un código sería un cambio incompatible y
pasa por la política de la página 09.
request_id es lo que hay que citar en cualquier incidente. doc_url apunta a la explicación del código
concreto.
Dos reglas que puede dar por buenas
Un fallo de negocio nunca llega como 5xx. Si recibe un 5xx, el problema es nuestro: o un defecto de
programación, o un servicio del que dependemos. No hay ningún caso en el que su saldo, su cupo, su
credencial o su cuerpo produzcan un 5xx.
Los errores temporales traen Retry-After en segundos. Si no lo trae, esperar no cambia nada: hay que
arreglar algo.
Los treinta y cuatro códigos, y qué hacer con cada uno
Son 34, agrupados por quién lo arregla. La última columna no es «¿reintentar?»: es la acción concreta, que
es lo único que su código necesita decidir al recibirlo. Cuando la acción es esperar, el plazo llega en
Retry-After y no hay que calcularlo.
Credencial y permisos
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
authentication_required | 401 | Falta la credencial o no viene como Authorization: Bearer … | Mande la cabecera con esa forma exacta. Reintentar sin cambiarla no sirve |
invalid_api_key | 401 | La clave no existe, está revocada o caducó | Compruebe en la consola que la clave sigue viva; si no, emita otra. No reintente |
ip_not_allowed | 403 | El origen de la petición no está en la lista de su clave | Añada ese origen a la lista de la clave, o llame desde uno que ya esté |
insufficient_scope | 403 | Su clave no tiene el permiso de esta operación | Lea WWW-Authenticate: dice cuál faltaba. Emita una clave que lo lleve (página 15) |
gateway_not_enabled | 403 | Su cuenta no tiene el módulo, no tiene cupo en ese entorno o su suscripción está cortada por impago (en los dos entornos: test llega al operador telefónico) | Pídanos el alta de ese entorno: el cupo es por entorno (página 18); si es el pago, regularícelo en la consola. La preparación de la consola lo muestra en «Cuenta habilitada». No reintente |
permission_denied | 403 | Prohibido por una regla que no es de permiso ni de origen | Escálelo con el request_id: no lo arregla ni la clave ni el origen |
Los tres motivos de invalid_api_key no se distinguen a propósito: decir cuál de los tres es ayudaría a
quien está probando claves. Un insufficient_scope declara en WWW-Authenticate qué permiso hacía falta.
Pago y cupo
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
insufficient_credits | 402 | Su saldo no cubre la operación, o el excedente está encendido y su cuenta no tiene medio de pago verificado | Recargue créditos, o agregue un medio de pago para que el excedente cuente, y reintente. Subir el tope de gasto no lo arregla |
spending_cap_reached | 402 | Su tope de gasto por excedente no admite este importe | Suba el tope de gasto de la cuenta. Recargar no lo arregla |
period_quota_exhausted | 429 | El techo de créditos del periodo está agotado | Espere lo que dice Retry-After, o pídanos un techo mayor para el periodo |
concurrency_limit_reached | 429 | Su techo de peticiones simultáneas está lleno | Espere Retry-After y baje sus peticiones en vuelo |
call_concurrency_limit_reached | 429 | Su techo de llamadas simultáneas está lleno | Espere a que cuelgue una llamada. Espaciar peticiones no libera este techo |
request_exceeds_period_quota | 409 | La petición mínima de la operación no cabe en el techo del periodo entero | Pídanos un techo mayor para el periodo. Esperar no lo arregla: no trae Retry-After |
request_exceeds_period_quota no es period_quota_exhausted: aquí el techo no se ha gastado, es que esta
operación no cabe en él ni con el periodo vacío. Por eso es 409 y no 429, y no trae plazo de espera. Los
dos solo pueden llegar si su cuenta tiene un techo de créditos por periodo: una cuenta de la beta nace sin
techo (página 18), y entonces lo que manda es el saldo.
🔴 El catálogo tiene tres 402, y los dos de esta tabla piden acciones opuestas: por eso son dos códigos.
El tercero, phone_number_suspended, es del número propio y está en la tabla siguiente. Con insufficient_credits hay que
recargar; con spending_cap_reached, recargar no sirve —el consumo por excedente sigue bloqueado en el
mismo punto— y lo que hay que subir es el límite. Un cliente que solo mire el estado HTTP no distinguirá los
dos; el que mire el code, sí. Ninguno de los dos lleva Retry-After: no es un plazo que se cumpla solo.
Los dos techos de concurrencia también son recursos distintos. El de llamadas puede estar lleno mientras el de peticiones está vacío, y la respuesta correcta es opuesta: espaciar peticiones, o esperar a que cuelgue una llamada.
🔴 Una clave ak_test_ también recibe los dos 402 de saldo y de tope. El entorno de pruebas ejecuta de verdad contra los
mismos proveedores, así que también consume créditos y también depende de su saldo y de su tope de gasto
(página 14). Pruebas y producción gastan del mismo saldo y ninguno tiene un techo
de créditos propio: lo que acota un bucle de pruebas es su tope de gasto, no un cupo del entorno.
Destino de prueba y número propio
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
test_destination_not_verified | 409 | Con una clave ak_test_, el destino no es uno de sus números de prueba verificados | Registre y verifique ese número como destino de prueba en la consola, o llame a uno que ya lo esté. No se le cobra |
phone_number_required | 409 | En live, su cuenta no tiene número propio, o su expediente falta, está incompleto o fue rechazado | Compre su número, o cree, complete o corrija el expediente en la consola. Reintentar no cambia nada |
phone_number_in_review | 409 | En live, su número o su expediente está en provisión o en revisión | No reintente en bucle: consulte el estado del número en la consola. Cuando esté aprobado, la misma petición funciona. No trae Retry-After: no es un plazo que podamos prometer |
phone_number_suspended | 402 | En live, su número está suspendido porque venció la gracia del pago de su cuota | Póngase al día con el pago de la cuenta; el número vuelve a servicio sin cambiar de clave |
Ninguno es 403: su credencial sirve en los cuatro. Los tres 409 dicen que lo que no está listo es el
destino o el número, y phone_number_suspended es 402 como el saldo porque lo arregla un pago.
Los cuatro salen antes de abrir el trabajo: no se cobran y no consumen su Idempotency-Key, así que,
arreglada la causa, reintente con la misma clave. En live ya no hay número de Asixto de reserva: sin
número propio listo, la llamada no sale (página 09). Qué poner en to y cómo dar de
alta un número de prueba, en la página 14.
Su petición
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
validation_error | 400 | El cuerpo no cumple el contrato. El mensaje dice qué campo | Corrija ese campo. Reintentar el mismo cuerpo da el mismo error |
payload_too_large | 413 | El cuerpo o el archivo supera el techo | Recorte o parta el contenido bajo el techo de la operación (página 15) |
unsupported_media_type | 415 | El tipo o el formato del archivo no se admite | Convierta el archivo a uno de los formatos que lista el mensaje |
document_unreachable | 422 | No pudimos descargar el documento que declaró | Con Retry-After, espere y reintente; sin él, arregle el enlace (URL firmada caducada, redirecciones) |
resource_not_found | 404 | La ruta o el trabajo no existen | Revise la ruta y el job_id. Un trabajo de otra cuenta responde igual |
method_not_allowed | 405 | La ruta existe pero no con ese método | Use el verbo que publica la referencia. Hoy no tiene emisor: no programe contra él |
rate_limited | 429 | Techo de tasa de la plataforma, no su cupo | Espere Retry-After. No hay saldo que leer: el cupo es compartido |
key_rate_limited | 429 | Ritmo de su clave; el saldo viaja en RateLimit | Espere Retry-After y regúlese con RateLimit (página 18) |
request_rejected | 400 | Rechazo sin causa más fina | Revise la petición; si no encuentra la causa, escálela con el request_id |
document_unreachable es un 422 y no un 400 ni un 503 porque el fallo es del recurso, no de su
petición ni de nuestro servicio: su URL firmada caducó, su almacenamiento está limitando la tasa o la cadena
de redirecciones no termina. Cuando la causa es transitoria viaja con Retry-After, y eso es lo que
distingue «reintente» de «arregle el enlace».
rate_limited no es su cupo: es el limitador de la plataforma, y aquí no hay un cupo propio que se libere.
key_rate_limited sí lo es —el ritmo de su clave— y su saldo viaja en RateLimit (página 18).
Los dos traen Retry-After, que es lo que hace el reintento correcto sin mirar nada más.
Estado del trabajo, de la clave de idempotencia y del recurso
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
request_in_flight | 409 | Otra petición con la misma clave sigue en curso | Consulte el trabajo (página 17) en vez de reintentar en bucle |
idempotency_key_reused | 409 | Reusó la clave para otra operación, o para la misma con otras variables | Use una clave distinta por operación. Nunca la comparta entre dos |
result_not_replayable | 409 | La operación se ejecutó y su resultado no se puede reenviar | Pida la operación con una clave nueva. El cobro anterior no se duplica |
job_not_completed | 409 | El trabajo de esa clave venció su plazo sin cerrarse | Pida la operación con una clave nueva: no hay resultado que recuperar |
agent_outside_schedule | 409 | El instante de la petición cae fuera del horario del agente | Reintente a partir de next_window_at, que viene en el cuerpo del error |
Los cinco son 409 porque el conflicto es con el estado del recurso, no con su autorización ni con su
cuerpo. request_in_flight sigue el precedente de la industria para una clave en curso.
agent_outside_schedule es el único código cuyo cuerpo trae un quinto campo, next_window_at, con la
apertura de la ventana siguiente en ISO-8601 UTC. Lleva una fecha y no un Retry-After porque aquí el plazo
se conoce: lo declara el horario del propio agente, no una avería. Los otros treinta y tres no traen el
campo, y la clave se omite en vez de viajar en null, así que su capa de transporte no tiene que preverlo.
result_not_replayable no es un 404: el trabajo existe y su cobro también; lo que no existe es una
copia de su salida. Hoy solo lo produce la síntesis de voz (página 15).
Nuestro lado
| Código | HTTP | Qué pasó | Qué hacer |
|---|---|---|---|
upstream_unavailable | 503 | Un servicio del que dependemos no responde | Espere Retry-After y reintente; con qué clave depende de la superficie (debajo). No se le cobra |
upstream_timeout | 504 | Aceptó la petición y no respondió a tiempo | La operación pudo ejecutarse. En POST /v1/calls, reintente con la misma clave; en el resto, con una nueva (debajo). En una operación síncrona no se le cobra; en POST /v1/calls se cobra solo si nos llega el cierre de la llamada (debajo) |
operation_not_priced | 500 | Defecto nuestro: la operación no tiene precio | Escálelo con el request_id. No reintente: no se arregla solo |
internal_error | 500 | Defecto nuestro: excepción no prevista | Escálelo con el request_id |
Un upstream_unavailable nunca se cobra, y un upstream_timeout tampoco, salvo en POST /v1/calls:
ahí un 504 se cobra si nos llega el cierre de la llamada —la liquida, como a cualquier llamada—; si el trabajo
vence sin él, se le devuelve lo reservado. Un upstream_unavailable también es lo que recibe cuando el proveedor
responde pero no entrega lo que se pidió —un análisis degradado, una transcripción descartada, un turno que
solo alcanza a disculparse—: para usted es el mismo hecho, no recibió lo que pidió y no se le cobra
(página 13).
Con qué clave reintentar un 5xx transitorio depende de dónde lo recibió:
| Superficie | Reintente con | Por qué |
|---|---|---|
POST /v1/calls, con 503 o con 504 | La misma clave, siempre | Ver debajo: una clave nueva tras un 504 puede llamar dos veces a la misma persona |
Una operación síncrona, cuando el 503 es al comprobar su cupo de peticiones simultáneas | La misma o una nueva: cualquiera vale | Sale antes de abrir el trabajo, así que la clave no quedó usada |
| Una operación síncrona en cualquier otro caso, y el turno del agente | Una clave nueva | El trabajo se cierra con el fallo, y repetir la clave le devuelve el mismo error sin volver a intentarlo (página 17) |
Si repite una clave cuyo trabajo ya terminó con un 5xx transitorio, recibe el mismo código con un mensaje que
le pide una clave nueva y sin Retry-After: esa clave republica su fallo en cada intento.
En POST /v1/calls el código le dice si la llamada salió:
503 upstream_unavailable: la llamada no se originó. No hubo marcado, se le devuelve lo reservado y la clave vuelve a estar libre: con la misma clave, la llamada se intenta de nuevo.504 upstream_timeout: la llamada pudo originarse. No sabemos si el teléfono sonó, así que el trabajo sigue abierto y no se le devuelve nada todavía. Con la misma clave recibe409 request_in_flightmientras se resuelve. Si nos llega el cierre de la llamada, la clave le devuelve su resultado y se cobra. Si el trabajo vence sin él —no salió, o salió y su cierre se perdió—, se le devuelve lo reservado y la clave responde409 job_not_completed: antes de pedirla con una clave nueva, confirme que la persona no recibió la llamada. Con una clave nueva mientras tanto podría llamar dos veces a la misma persona.
Fuera de las llamadas, upstream_timeout también significa que la operación puede haberse ejecutado y
nosotros no tenemos su resultado. En una operación síncrona no se le cobra, y el coste de ese intento lo
absorbemos nosotros.
Un 500 es lo único que conviene escalar: significa que hay un defecto en nuestro código, no en su
integración. Cítenos el request_id.
Cómo escribir el manejo de errores
2xx→ siga. Apunteusagesi va a repercutir el coste.5xxconRetry-After→ espere lo que dice y reintente. EnPOST /v1/calls, con la misma clave; en el resto, con una nueva.429→ espere lo que diceRetry-After. Si escall_concurrency_limit_reached, lo que tiene que terminar es una llamada, no una petición.402→ pare y mire elcode: o recarga, o sube su tope, o paga la cuota de su número.409 request_in_flight→ su operación anterior sigue viva. Consulte el trabajo (página 17) en vez de reintentar en bucle.409 agent_outside_schedule→ agende el reintento connext_window_atdel cuerpo. Es el único409que le dice cuándo volver.- El resto de
4xx→ es su petición o su credencial. Reintentar no cambia nada.
Lo que no conviene: reintentar un 4xx sin cambiar nada, o reintentar sin respetar Retry-After.