Sección 17
Trabajos, resultados y reintentos
Toda petición a /v1 crea un trabajo, y su identificador viaja en el sobre como job_id. En las cinco
operaciones síncronas eso es un detalle de contabilidad; en las dos asíncronas es el hilo por el que se sigue
la operación.
GET /v1/jobs/{job_id}
Es la red de seguridad de lectura: quien no monte receptor de webhooks tiene que poder preguntar por su trabajo y recibir una respuesta que distinga todos los casos.
curl -sS -X GET "$ASIXTO_BASE_URL/v1/jobs/job_9f2a7c41" \
-H "Authorization: Bearer $ASIXTO_API_KEY"{
"request_id": "req_9c67575147ae560b557cf5f3",
"job_id": "cd5e91f3-371e-4ffc-b168-a0beb22f31a5",
"status": "succeeded",
"operation": "agent_turn",
"environment": "live",
"created_at": "2026-08-30T00:43:59.601Z",
"started_at": "2026-08-30T00:43:59.640Z",
"finished_at": "2026-08-30T00:44:01.354Z",
"result_state": "available",
"result": { "status": "completed", "stream_id": "cd5e91f3-…", "last_sequence": 1 },
"usage": { "operation": "agent_turn", "unit": "request", "quantity": 1, "credits": 11 },
"credits_charged": 11,
"error": null
}Necesita el permiso jobs:read. Consultar no es ejecutar, y por eso no reutiliza el permiso de la
operación que creó el trabajo: es la única entrada de la superficie que no gasta créditos ni cupo, así que
puede concedérsela a su panel de soporte sin concederle marcar llamadas ni sintetizar audio.
Los cuatro campos que comparte con el sobre —request_id, job_id, usage y result— significan lo
mismo y se llaman igual, para que escriba una sola capa de transporte y la use en el acuse, en la
repetición de una clave y en esta consulta.
request_id aquí es el de esta consulta, no el de la petición que creó el trabajo.
Lo que no se publica, y es deliberado: la entrada de su petición —que no se persiste— y la clave de API
que lo creó. El environment sí, porque un trabajo no cruza de entorno y una integración que promociona de
pruebas a producción necesita poder verlo.
status: el ciclo de vida
| Valor | Qué significa |
|---|---|
queued | Aceptado, sin empezar |
running | En curso |
succeeded | Terminó bien |
failed | Terminó con un fallo. error.code dice cuál |
expired | Se agotó su plazo sin cerrarse |
Los tres últimos son terminales. expired no es lo mismo que failed: un trabajo cuyo plazo vence libera
su reserva de créditos sin que nadie lo haya cerrado, y confundirlo con un fallo haría imposible auditar esa
liberación.
result_state: cinco casos, no un result que a veces es nulo
Un nulo colapsaría situaciones cuya respuesta correcta es distinta.
| Valor | Qué significa | Qué hacer |
|---|---|---|
pending | El trabajo no ha terminado | Vuelva a consultar |
available | El resultado está en result | Úselo |
expired | Venció su ventana de retención | Pida la operación con una clave nueva |
not_stored | Esa operación no guarda su resultado por diseño | Pídala con una clave nueva |
unavailable | Terminó sin resultado utilizable | Mire status y error |
La diferencia entre expired y not_stored importa: en el primer caso hubo un resultado y se purgó; en
el segundo nunca hubo copia. Hoy not_stored solo lo produce la síntesis de voz.
Cuánto vive el resultado
El resultado tiene una ventana de retención de 24 h, contada desde que el trabajo se cierra. Dentro de la
ventana, repetir su clave de idempotencia le devuelve el mismo resultado sin volver a cobrar; pasada, el
resultado se purga y result_state pasa a expired.
credits_charged sobrevive a esa purga, y se publica aparte de usage justamente por eso: pasada la
ventana queda el importe y no el desglose. Con solo usage, un trabajo consultado al día siguiente parecería
no haber costado nada.
El resto de las ventanas del contrato están en la página 13.
Repetir una clave de idempotencia: caso por caso
Es la misma decisión que toma la consulta de un trabajo, vista desde el otro lado.
| Estado del trabajo anterior | Qué recibe |
|---|---|
| Todavía en curso | 409 request_in_flight |
| Terminó bien y su resultado se guarda | El mismo job_id, el mismo usage y el resultado. Sin cobrar otra vez |
| Terminó bien y su resultado no se guarda | 409 result_not_replayable. El cobro no se duplica |
| Terminó bien y su resultado venció la ventana de retención de 24 h | 409 result_not_replayable, y el mensaje nombra la ventana. Pídala con una clave nueva |
| Terminó con un fallo | El mismo fallo que la primera vez |
| Caducó | 409 job_not_completed |
La clave se usó para otra operación, o con otras variables | 409 idempotency_key_reused |
Que un fallo se republique tal cual es lo que hace que reintentar con la misma clave sea seguro: no se vuelve a ejecutar nada ni se vuelve a cobrar. Si lo que quiere es volver a intentarlo de verdad, use una clave nueva.
Cómo seguir una operación asíncrona
Dos formas, y no son excluyentes:
- Webhooks (página 11): le avisamos cuando el trabajo llega a un estado terminal. Es lo que evita consultar en bucle.
- Consulta de
GET /v1/jobs/{job_id}: siempre disponible, y es lo que usa quien todavía no ha montado el receptor o quien está diagnosticando un caso concreto.
Si va a consultar en bucle, espacie las consultas: la consulta no gasta créditos pero sí cuenta para el techo de tasa de la plataforma (página 16).
Para el turno del agente hay además un tercer camino, que es leer la respuesta mientras se genera por el
stream_url del acuse. Leerla no cuesta nada, ni la primera vez ni al reanudar
(página 12).
Ese stream_url lo autoriza el mismo jobs:read que esta consulta, y no el permiso con el que abrió el
turno: una clave que puede conversar pero no leer se queda sin la respuesta que ya pagó. Los detalles del
permiso están en la página 15.
Qué hacer cuando se pierde una respuesta
Es el caso para el que existe todo lo anterior: su proceso murió, su tiempo de espera se agotó, su red falló.
- Repita la petición con la MISMA clave de idempotencia. Si la operación ocurrió, recibe su resultado sin pagarlo dos veces; si no ocurrió, se ejecuta ahora.
- Si recibe
409 request_in_flight, la anterior sigue viva. En una operación síncrona, espere lo que indicaRetry-Aftery repita con la misma clave, o consulte el trabajo si conserva sujob_id. En una llamada,Retry-Afteres el tiempo máximo que le queda (puede durar hasta 900 s,max_duration_seconds, más un margen de cierre): no reintente antes, porque mientras habla solo consume su límite de peticiones. Espere al webhook del cierre o a que pase ese tiempo, y repita entonces (Si lo que perdió es el acuse de una llamada). - Si recibe
409 result_not_replayableojob_not_completed, la operación no se puede recuperar: pídala con una clave nueva.
Si lo que perdió es el acuse de una llamada
Una llamada aceptada sigue en curso hasta que cuelga, y su trabajo no es terminal mientras tanto. Por eso,
mientras habla, la misma Idempotency-Key responde 409 request_in_flight: el 202 del acuse no se
repite. Ese 409 lleva en Retry-After los segundos que le quedan como máximo a la llamada, que es una
cota: si cuelga antes, la misma clave ya devuelve su resultado; mientras siga hablando, devuelve el mismo
409. Cuando cuelga, la misma clave devuelve el resultado guardado de la llamada
(status completed, duration_seconds, billed_minutes) con el job_id original y los mismos warnings
del acuse; si la llamada falló, devuelve el mismo fallo.
Si perdió el 202, perdió también el job_id, y mientras la llamada vive la API no se lo devuelve: el
409 no lo trae y no hay un listado de trabajos en /v1, solo GET /v1/jobs/{job_id}. Lo recupera de dos
formas, las dos al colgar:
- El webhook del cierre (
job.succeeded,job.failedojob.expired) traejob_idy, endata.metadata, elmetadataque envió al originarla. Envíe en él su identificador y podrá casar el cierre aunque no guardara nada nuestro. - Repetir la petición con la misma clave cuando la llamada ya terminó.
En la consola, el listado de trabajos filtra por entorno, estado, operación y fechas, pero no por clave de idempotencia.
Lo que no conviene: reintentar sin clave de idempotencia. Es la única forma de acabar pagando dos veces por lo mismo.