16Errores de la API

Sección 16

Errores de la API

.md

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

JSON
{
  "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ódigoHTTPQué pasóQué hacer
authentication_required401Falta la credencial o no viene como Authorization: Bearer …Mande la cabecera con esa forma exacta. Reintentar sin cambiarla no sirve
invalid_api_key401La clave no existe, está revocada o caducóCompruebe en la consola que la clave sigue viva; si no, emita otra. No reintente
ip_not_allowed403El origen de la petición no está en la lista de su claveAñada ese origen a la lista de la clave, o llame desde uno que ya esté
insufficient_scope403Su clave no tiene el permiso de esta operaciónLea WWW-Authenticate: dice cuál faltaba. Emita una clave que lo lleve (página 15)
gateway_not_enabled403Su 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_denied403Prohibido por una regla que no es de permiso ni de origenEscá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ódigoHTTPQué pasóQué hacer
insufficient_credits402Su saldo no cubre la operación, o el excedente está encendido y su cuenta no tiene medio de pago verificadoRecargue 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_reached402Su tope de gasto por excedente no admite este importeSuba el tope de gasto de la cuenta. Recargar no lo arregla
period_quota_exhausted429El techo de créditos del periodo está agotadoEspere lo que dice Retry-After, o pídanos un techo mayor para el periodo
concurrency_limit_reached429Su techo de peticiones simultáneas está llenoEspere Retry-After y baje sus peticiones en vuelo
call_concurrency_limit_reached429Su techo de llamadas simultáneas está llenoEspere a que cuelgue una llamada. Espaciar peticiones no libera este techo
request_exceeds_period_quota409La petición mínima de la operación no cabe en el techo del periodo enteroPí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ódigoHTTPQué pasóQué hacer
test_destination_not_verified409Con una clave ak_test_, el destino no es uno de sus números de prueba verificadosRegistre 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_required409En live, su cuenta no tiene número propio, o su expediente falta, está incompleto o fue rechazadoCompre su número, o cree, complete o corrija el expediente en la consola. Reintentar no cambia nada
phone_number_in_review409En live, su número o su expediente está en provisión o en revisiónNo 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_suspended402En live, su número está suspendido porque venció la gracia del pago de su cuotaPó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ódigoHTTPQué pasóQué hacer
validation_error400El cuerpo no cumple el contrato. El mensaje dice qué campoCorrija ese campo. Reintentar el mismo cuerpo da el mismo error
payload_too_large413El cuerpo o el archivo supera el techoRecorte o parta el contenido bajo el techo de la operación (página 15)
unsupported_media_type415El tipo o el formato del archivo no se admiteConvierta el archivo a uno de los formatos que lista el mensaje
document_unreachable422No pudimos descargar el documento que declaróCon Retry-After, espere y reintente; sin él, arregle el enlace (URL firmada caducada, redirecciones)
resource_not_found404La ruta o el trabajo no existenRevise la ruta y el job_id. Un trabajo de otra cuenta responde igual
method_not_allowed405La ruta existe pero no con ese métodoUse el verbo que publica la referencia. Hoy no tiene emisor: no programe contra él
rate_limited429Techo de tasa de la plataforma, no su cupoEspere Retry-After. No hay saldo que leer: el cupo es compartido
key_rate_limited429Ritmo de su clave; el saldo viaja en RateLimitEspere Retry-After y regúlese con RateLimit (página 18)
request_rejected400Rechazo sin causa más finaRevise 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ódigoHTTPQué pasóQué hacer
request_in_flight409Otra petición con la misma clave sigue en cursoConsulte el trabajo (página 17) en vez de reintentar en bucle
idempotency_key_reused409Reusó la clave para otra operación, o para la misma con otras variablesUse una clave distinta por operación. Nunca la comparta entre dos
result_not_replayable409La operación se ejecutó y su resultado no se puede reenviarPida la operación con una clave nueva. El cobro anterior no se duplica
job_not_completed409El trabajo de esa clave venció su plazo sin cerrarsePida la operación con una clave nueva: no hay resultado que recuperar
agent_outside_schedule409El instante de la petición cae fuera del horario del agenteReintente 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ódigoHTTPQué pasóQué hacer
upstream_unavailable503Un servicio del que dependemos no respondeEspere Retry-After y reintente; con qué clave depende de la superficie (debajo). No se le cobra
upstream_timeout504Aceptó la petición y no respondió a tiempoLa 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_priced500Defecto nuestro: la operación no tiene precioEscálelo con el request_id. No reintente: no se arregla solo
internal_error500Defecto nuestro: excepción no previstaEscá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ó:

SuperficieReintente conPor qué
POST /v1/calls, con 503 o con 504La misma clave, siempreVer 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áneasLa misma o una nueva: cualquiera valeSale 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 agenteUna clave nuevaEl 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 recibe 409 request_in_flight mientras 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 responde 409 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

  1. 2xx → siga. Apunte usage si va a repercutir el coste.
  2. 5xx con Retry-After → espere lo que dice y reintente. En POST /v1/calls, con la misma clave; en el resto, con una nueva.
  3. 429 → espere lo que dice Retry-After. Si es call_concurrency_limit_reached, lo que tiene que terminar es una llamada, no una petición.
  4. 402 → pare y mire el code: o recarga, o sube su tope, o paga la cuota de su número.
  5. 409 request_in_flight → su operación anterior sigue viva. Consulte el trabajo (página 17) en vez de reintentar en bucle.
  6. 409 agent_outside_schedule → agende el reintento con next_window_at del cuerpo. Es el único 409 que le dice cuándo volver.
  7. 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.