Sección 15
Referencia de las operaciones
Las ocho operaciones de /v1, con su permiso, su unidad de cobro y su contrato de entrada. La forma del
sobre y de los errores está en las páginas 14 y 16.
Todos los nombres de campo van en snake_case, igual que el sobre de uso y el cuerpo de error.
Resumen
| Operación | Permiso | Se factura como | Unidad | Respuesta |
|---|---|---|---|---|
POST /v1/vision/analysis | vision:analyze | image_analysis | imagen | 200 con sobre |
POST /v1/documents/analysis | documents:analyze | document_analysis | documento | 200 con sobre |
POST /v1/video/analysis | video:analyze | video_analysis | video | 200 con sobre |
POST /v1/audio/transcriptions | audio:transcribe | audio_transcription | segundo | 200 con sobre |
POST /v1/audio/speech | speech:synthesize | tts_synthesis | carácter | 200 binario |
POST /v1/text/sentiment | sentiment:analyze | sentiment_analysis | petición | 200 con sobre |
POST /v1/calls | calls:initiate | voice_call_minute | minuto | 202 con acuse |
POST /v1/agent/turns | agent:converse | agent_turn | petición | 202 con acuse |
GET /v1/jobs/{job_id} | jobs:read | no se cobra | — | 200 (página 17) |
GET /v1/agents | agents:read | no se cobra | — | 200 con la lista de sus agentes activos |
Las seis primeras responden con el resultado en la misma llamada. Las dos asíncronas —la llamada y el turno— responden un acuse y el resultado llega después. Las dos últimas son LECTURAS y no cobran nada.
Hay una entrada más que no aparece arriba porque no es una operación que se pida, sino la lectura de una que
ya pidió: el stream del turno, en GET /v1/agent/streams/{job_id}, cuya URL exacta le llega en el acuse. Lo
autoriza jobs:read, y su contrato está en la página 19.
De dónde sale el agent_id
POST /v1/agent/turns y POST /v1/calls exigen un agent_id, y GET /v1/agents es el que se lo da: devuelve
sus agentes activos con id, name y active. Nada más — la configuración del agente (capacidades,
reglas, canales, voz) se gobierna en la consola y no viaja por esta API.
curl -sS -X GET "$ASIXTO_BASE_URL/v1/agents" \
-H "Authorization: Bearer $ASIXTO_API_KEY"{
"data": [{ "id": "a4f1c2e0-6d3b-4e7a-9c21-8f0e5b7d4a13", "name": "Cobranza", "active": true }],
"total": 1
}Solo lista agentes que pueden atender un turno ahora mismo: uno desactivado no aparece, porque ofrecerle un
agent_id que la operación siguiente rechaza con 400 sería peor que no ofrecerlo. Crear o editar agentes
no está en /v1: eso vive en la consola, donde está su único escritor.
Devuelve hasta 100 agentes. total es el número real de activos, así que si su cuenta tuviera más de cien
lo vería ahí, y la respuesta traería además omitted con cuántos no cupieron. No hay cursor ni páginas: los
agentes se crean a mano en la consola y cien es holgado para cualquier cuenta. Si llega a ese punto, escríbanos
(página 10) — preferimos hablarlo antes que publicar un formato de paginación que después
no podríamos cambiar. El recorte conserva el orden, así que el agent_id que guarde no se le mueve.
Ojo con las claves que ya tenía emitidas: agents:read se concede al emitir, así que una clave anterior a
este permiso responde 403 insufficient_scope aquí. Emita una nueva y rótela (página 9).
La lista se sirve con hasta 60 segundos de retraso: es la misma lectura que usa la consola y va cacheada por
cuenta. Un agente que active ahora puede tardar ese minuto en aparecer aquí, así que no encadene «crear en la
consola» con «listar por API» sin ese margen. Los agent_id no cambian, de modo que una vez obtenido puede
guardarlo: esta ruta es para descubrirlo, no para consultarla en cada turno.
Los diez permisos de una clave
Una clave lleva los permisos que se le concedieron al emitirla, y son diez en total: uno por capacidad,
más los dos de lectura. Una clave sin ninguno no autoriza nada y toda petición responde 403 insufficient_scope.
| Permiso | Qué autoriza | Qué no autoriza |
|---|---|---|
vision:analyze | Analizar una imagen | Documentos, audio ni ninguna otra capacidad |
documents:analyze | Analizar un documento que usted aloja por URL | Subir el archivo, ni analizar imágenes |
video:analyze | Analizar un video que usted aloja por URL | Documentos ni imágenes: cada capacidad lleva el suyo |
audio:transcribe | Convertir audio a texto | Generar audio: eso es speech:synthesize |
speech:synthesize | Generar audio a partir de texto | Leer audio. Leer y generar son permisos distintos a propósito |
sentiment:analyze | Analizar el tono de un texto | Nada del turno del agente: el turno no lo hace por su cuenta |
calls:initiate | Originar una llamada de voz con su agente | Leer la llamada: eso es jobs:read. Colgarla no existe en la API |
jobs:read | Consultar un trabajo y leer el stream de un turno | Crear nada. Es la única entrada que no gasta créditos ni cupo |
agent:converse | Abrir un turno de conversación con su agente | Leer la respuesta de ese turno: leerla la autoriza jobs:read |
agents:read | Listar sus agentes activos para obtener el agent_id | Crear, editar ni desactivar un agente: eso es de la consola |
calls:initiate es «originar», no «llamadas». Un permiso de recurso genérico habría cubierto también
leer o colgar, que son cosas que se pueden querer conceder sin conceder la de marcar: es la única operación
de la superficie que gasta dinero por minuto en un tercero y expone su número al destinatario.
No hay verbo para colgar una llamada en curso, y no lo damos por supuesto. La API no publica ninguna ruta
que corte una llamada ya originada: la llamada termina cuando la conversación termina, cuando se agota el plazo
del trabajo o cuando un operador de su empresa pulsa el botón de parada desde la consola. Si necesita
detener una llamada, ese es hoy el único camino. Tampoco hay estado canceled: los cinco valores de status
son los de la página 17 y ninguno significa «cancelado».
jobs:read es el que le deja leer lo que pagó
Cubre dos entradas, y por eso es el que más falta se echa cuando no está: la consulta del trabajo
(GET /v1/jobs/{job_id}, página 17) y la lectura del stream del turno, cuyo stream_url viene
en el acuse de POST /v1/agent/turns.
Esa segunda ruta admite dos credenciales distintas para la misma autorización: su clave de API con
jobs:read, y el token de sesión de nuestra consola con su propio permiso de lectura de conversación. El
criterio es el mismo en los dos casos —quien paga el turno puede leer su respuesta— y un trabajo de otra
cuenta responde igual que uno inexistente.
🔴 Sin jobs:read, un turno se paga y no se puede leer, y por eso toda clave nace con él. La emisión
añade siempre jobs:read y agents:read a lo que usted marque: son las dos lecturas que no gastan
créditos ni cupo, y sin ellas quien integra no puede leer lo que pagó ni descubrir el agent_id que las
demás rutas le exigen (página 9). La otra cara —la que hay que tener presente al
emitir— es que esas dos no se pueden dejar fuera: si su política es conceder el mínimo indispensable,
cuente con ellas dentro y no espere una clave que las excluya. La expansión ocurre solo al emitir, así
que una clave emitida antes de que uno de esos dos permisos existiera no lo gana por sí sola: hay que emitir
otra y rotarla.
agent:converse no se concede «por si acaso»
Es la capacidad más cara de la superficie y la única que no se puede deshacer: abrir un turno gasta créditos,
invoca las herramientas de su servidor —que pueden escribir en sus sistemas— y ya ocurrió. Concederlo a
una clave emitida para transcribir audio es regalar eso. Es lo contrario de jobs:read: ahí lo que faltaba
era una lectura inocua, aquí lo que sobra es una escritura irreversible.
Los permisos de una clave ya emitida no se editan desde la consola: si le falta uno, emita otra con lo que necesita, despliegue y revoque la anterior. Cada entorno admite dos claves activas justo para eso.
POST /v1/vision/analysis
Análisis de una imagen. multipart/form-data con un solo archivo.
| Campo | Dónde | Obligatorio | Límite |
|---|---|---|---|
file | parte del multipart | sí | 10 MiB |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/vision/analysis" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Idempotency-Key: img-4471" \
-F "file=@albaran.jpg"result: analysis (la descripción), summary y degraded. Si el modelo no pudo leer la imagen, la
operación no se cobra y responde el error de proveedor no disponible en vez de un análisis vacío.
POST /v1/documents/analysis
Análisis de un documento que usted aloja. Se envía la URL, no el archivo: su almacenamiento ya sabe servirlo con una URL firmada, y pedirle que nos lo suba sería mover megabytes dos veces.
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
document_url | texto | sí | 1–2048 caracteres |
mime_type | texto | sí | 1–255 caracteres |
filename | texto | sí | 1–255 caracteres |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/documents/analysis" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: doc-4471" \
-d '{"document_url":"https://archivos.suempresa.com/contrato.pdf","mime_type":"application/pdf","filename":"contrato.pdf"}'result: extracted_text, summary y degraded.
Si no podemos descargar su documento —URL firmada caducada, su almacenamiento limitando la tasa, una cadena
de redirecciones que no termina— recibe 422 document_unreachable, que existe para atribuirle el fallo a
quien lo puede arreglar: la petición está bien escrita y el recurso al que apunta no se pudo procesar.
POST /v1/video/analysis
Análisis de un video que usted aloja, igual que el documento: se manda la URL en JSON, no el archivo.
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
video_url | texto | sí | 1–2048 caracteres |
mime_type | texto | sí | 1–255 caracteres, y ha de ser un tipo video/… |
filename | texto | sí | 1–255 caracteres |
duration_seconds | entero | no | 1 a 600 segundos |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/video/analysis" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: video-4471" \
-d '{"video_url":"https://archivos.suempresa.com/entrega.mp4","mime_type":"video/mp4","filename":"entrega.mp4","duration_seconds":45}'result: analysis (la descripción), summary y frames_analyzed. Un análisis que no se pudo hacer no se
cobra: responde el error en vez de un informe vacío.
filename es solo para el informe y el registro: no se usa para resolver nada. mime_type que no sea de
video se rechaza, porque esta operación analiza video y la imagen tiene la suya.
Los dos techos de la entrada
50 MiB y 600 segundos, y pasado cualquiera de los dos se rechaza nombrándolo, nunca recortando en silencio: un análisis de parte del video se cobraría igual que el completo.
| Techo | Cómo se comprueba | Qué recibe si se pasa |
|---|---|---|
| 50 MiB | Un sondeo del enlace que usted declara | 413 payload_too_large, con los bytes que anuncia su origen |
| 600 segundos | La duración que usted declara en duration_seconds | 400 validation_error, que nombra el campo |
duration_seconds es opcional porque la declara usted: no sostenemos los bytes del video, así que no
podemos medirla por nuestra cuenta. Cuando falta, el techo que acota lo que llega es el de tamaño. Y
declararla de menos no abarata nada, porque el precio no depende de ella.
🔴 La tarifa es plana: 270 créditos por video (página 12). Un video de diez segundos y uno de cinco minutos cuestan lo mismo. Por eso la entrada tiene techos y por eso se rechaza en vez de recortar.
POST /v1/audio/transcriptions
Audio a texto. multipart/form-data, y el idioma va en la query.
| Campo | Dónde | Obligatorio | Límite |
|---|---|---|---|
file | parte del multipart | sí | 25 MiB |
language | query | no | ISO 639-1, 2–5 caracteres |
Formatos admitidos: FLAC, MP3, MP4, MPEG, MPGA, M4A, OGG, WAV y WEBM. Otro tipo recibe
415 unsupported_media_type con la lista, en vez de fallar más adelante.
curl -sS -X POST "$ASIXTO_BASE_URL/v1/audio/transcriptions?language=es" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Idempotency-Key: nota-4471" \
-F "file=@nota.ogg"Mande language solo con evidencia real del idioma. Forzar uno que no es el del audio no falla: el
modelo traduce. Medido contra el proveedor — una nota en inglés con language=es vuelve en español. Sin
el parámetro se autodetecta, que es el comportamiento correcto por omisión.
Se cobran 10 segundos como mínimo, porque es lo que factura el proveedor: una nota de tres segundos
—el caso normal en un chat— cuesta diez. Va en usage.quantity, así que se puede conciliar.
result: text y filtered_hallucination. Un texto vacío con ese indicador puesto significa que
descartamos lo único que devolvió el proveedor: eso no se cobra. Un texto vacío sin el indicador es un
audio sin habla, y eso sí es un resultado.
POST /v1/audio/speech
Texto a voz. Devuelve el audio como cuerpo binario, siguiendo la convención de la industria, así que el sobre viaja en cabeceras.
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
text | texto | sí | 1–5.000 caracteres |
voice_id | texto | sí | 1–100 caracteres |
model_id | texto | no | 1–100 caracteres |
language_code | texto | no | 2–10 caracteres |
voice_settings | objeto | no | se reenvía sin interpretarlo |
speed | número | no | 0,25 a 4 |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/audio/speech" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: voz-4471" \
-d '{"text":"Su pedido llega el martes","voice_id":"es-CO-natural","speed":1}' \
--output respuesta.mp3 -D cabeceras.txt| Cabecera | Qué trae |
|---|---|
X-Asixto-Request-Id | lo mismo que request_id |
X-Asixto-Job-Id | lo mismo que job_id |
X-Asixto-Usage-Operation | lo mismo que usage.operation |
X-Asixto-Usage-Unit | lo mismo que usage.unit |
X-Asixto-Usage-Quantity | lo mismo que usage.quantity |
X-Asixto-Usage-Credits | lo mismo que usage.credits |
X-Asixto-Audio-Duration-Ms | duración del audio |
X-Asixto-Chars-Input | caracteres que usted envió |
X-Asixto-Chars-Normalized | en qué se convirtió su texto |
X-Asixto-Chars-Billed | caracteres cobrados |
Las tres últimas están para que entienda por qué se le cobran más caracteres de los que escribió: el proveedor cobra el texto normalizado, y la normalización expande —«Dr.» se dice «Doctor», «999» se dice «novecientos noventa y nueve»—.
🔴 Es la única operación cuyo resultado no se puede reenviar. El resultado es el audio, y guardarlo
significaría megabytes por petición y una copia durable de lo que usted dictó. Repetir su clave de
idempotencia recibe 409 result_not_replayable y el cobro no se duplica: si necesita el audio otra vez,
pida la síntesis con una clave nueva.
POST /v1/text/sentiment
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
text | texto | sí | 1–10.000 caracteres |
customer_id | texto | no | 1–128 caracteres |
channel | texto | no | 1–64 caracteres |
region_id | texto | no | 1–64 caracteres |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/text/sentiment" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sent-4471" \
-d '{"text":"Llevo tres días esperando y nadie me responde.","customer_id":"cliente-12345"}'result: sentiment, score y source. customer_id es su identificador y es opaco para nosotros: se
usa para la tendencia del sentimiento de ese cliente, nunca para resolver nada de nuestra base.
POST /v1/calls · asíncrona
Origina una llamada de voz con su agente. Responde 202 con un acuse: la conversación todavía no ha
empezado.
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
to | texto | sí | E.164 con + y país |
agent_id | texto | sí | identificador de un agente de su cuenta |
max_duration_seconds | entero | no | 1 a 900; sin él, 300 |
topic | texto | no | hasta 200 caracteres |
variables | objeto | no | ver contexto por petición |
metadata | objeto | no | ídem |
curl -sS -X POST "$ASIXTO_BASE_URL/v1/calls" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: llamada-4471" \
-d '{"to":"+573001112233","agent_id":"a4f1c2e0-6d3b-4e7a-9c21-8f0e5b7d4a13","max_duration_seconds":300,"topic":"cobro de la factura 4471"}'Acuse: call_id, status (queued), max_duration_seconds, authorized_minutes y from.
max_duration_seconds del acuse puede ser MENOR que el que pidió: es la forma de saber, antes de que
suene el teléfono, que su saldo solo paga parte de la conversación. La duración fuera de rango se
rechaza, no se recorta en silencio, porque es lo que decide cuántos créditos se reservan.
from es el número que verá el destinatario. Con ak_test_ es nuestro número compartido y to tiene que
ser uno de sus números de prueba verificados (si no, 409 test_destination_not_verified). Con ak_live_ es
el suyo, y sin él listo la llamada no sale: 409 phone_number_required, 409 phone_number_in_review o
402 phone_number_suspended. Qué hacer en cada caso, en la página 14.
Si la conducta de pruebas transfiere a un número que ya no está verificado, la llamada sale sin
transferencia y el acuse lo dice en warnings:
{ "…": "…", "warnings": { "ignored_variables": [], "transfer_disabled": { "reason": "test_destination_not_verified" } } }Repetir la petición con la misma Idempotency-Key no repite el acuse. Mientras la llamada vive, su
clave responde 409 request_in_flight, sin job_id, y su Retry-After es el tiempo máximo que le queda a
la llamada (la duración autorizada que resta más un margen de cierre), no unos segundos. Cuando cuelga,
devuelve el resultado guardado con el job_id original y los mismos warnings del acuse: call_id,
status (completed), duration_seconds y billed_minutes. Cómo recuperar el job_id si perdió el acuse, en la
página 17.
POST /v1/agent/turns · asíncrona
Un turno de conversación con su agente. Responde 202 con la ruta por la que se lee la respuesta mientras
se genera.
| Campo | Tipo | Obligatorio | Límite |
|---|---|---|---|
message | texto | sí | 1–20.000 caracteres |
channel | texto | sí | uno de los canales admitidos |
agent_id | texto | sí | identificador de un agente de su cuenta |
conversation_external_id | texto | no | 1–128 caracteres |
history | lista | no | hasta 40 mensajes y 20.000 caracteres sumados |
customer | objeto | no | ver abajo |
variables | objeto | no | ver contexto por petición |
metadata | objeto | no | ídem |
Canales admitidos
Son estos nueve: whatsapp, facebook_messenger, instagram, telegram, email, sms, voice,
chatbot_web y groups. Van en minúsculas y tal cual —WhatsApp no vale—, y cualquier valor fuera de esa
lista recibe 400 validation_error con ella.
De dónde viene el mensaje es una declaración suya, no un canal que nosotros conectemos: no mandamos nada por él. Lo que hace es cambiar cómo se le habla al cliente final —un correo no se redacta como un mensaje de chat—, así que declare el real.
Cada mensaje de history lleva role (customer o agent), content y un timestamp opcional en ISO
8601. El rol es obligatorio: sin él, lo que dijo el agente se leería como algo que dijo el cliente y el
modelo razonaría sobre una conversación que nunca ocurrió. El orden lo da la lista, no la fecha.
customer acepta external_id (1–128), name (1–200), phone (1–32) y email (1–320). Son datos suyos y
opacos para nosotros: se le pasan al agente para que sepa con quién habla.
🔴 conversation_external_id y history son excluyentes. Dos fuentes para la misma conversación es una
ambigüedad que nadie puede resolver bien, y la que perdiera se descartaría en silencio.
- Con
conversation_external_id: nosotros mantenemos la sesión. Usted manda solo el mensaje nuevo y la nombra con su identificador, así que el primer turno ya puede continuar una conversación sin una ida y vuelta previa. La clave es única por cuenta, así que nadie más ve la suya. La ventana vive 24 h de inactividad (página 13). - Con
history: usted lleva la conversación y la envía en cada turno.
curl -sS -X POST "$ASIXTO_BASE_URL/v1/agent/turns" \
-H "Authorization: Bearer $ASIXTO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: turno-4471" \
-d '{"message":"¿Cuál es el estado de mi pedido #12345?","channel":"whatsapp","agent_id":"a4f1c2e0-6d3b-4e7a-9c21-8f0e5b7d4a13","conversation_external_id":"cliente-12345"}'Acuse: stream_id, status y stream_url. stream_url es la ruta por la que se lee la respuesta mientras
se genera, y su contrato —eventos, forma de cada trama y reanudación— está en la
página 19.
El turno se cobra por respuesta generada, con un precio único (página 12). Leer la respuesta no cuesta nada, ni la primera vez ni al reanudar: el cobro sigue a la generación, no a la entrega. Si su cliente se desconecta a mitad, el turno cuesta lo mismo.
El contexto por petición: variables y metadata
Las dos surten a POST /v1/calls y a POST /v1/agent/turns, con la MISMA forma y los mismos techos, y
significan cosas opuestas:
variables | metadata | |
|---|---|---|
| ¿La ve el agente? | Sí: rellena los huecos de su configuración y las dice | No, nunca |
| ¿Vuelve en el webhook? | No | Sí, en data.metadata |
| Para qué es | personalizar lo que se dice | conciliar con su propio sistema |
Los techos son los mismos para las dos: hasta 20 claves, cada clave en minúsculas, dígitos y _
empezando por letra y hasta 40 caracteres (loan_id, nombre_cliente), cada valor de hasta 512
caracteres y el objeto entero hasta 4 KB. Los valores van en texto, número o booleano —se guardan como
texto— y no admiten objetos ni listas dentro: una clave anidada recibe 400 validation_error que la
nombra, en vez de descartarse en silencio.
variables: los huecos de la configuración de su agente
En la consola, el saludo y el mensaje de apertura de un agente pueden llevar huecos con la forma
{{clave}}. Lo que manda en variables es lo que los rellena, en esta petición y solo en esta:
{ "to": "+573001112233", "agent_id": "…", "variables": { "nombre_cliente": "Ana", "monto": "1.250.000" } }Con el saludo Hola {{nombre_cliente}}, le llamo por su saldo de {{monto}}, el agente dice
Hola Ana, le llamo por su saldo de 1.250.000.
Las dos reglas que le importan, y son opuestas a propósito:
- 🔴 Falta una que el agente declara ⇒
400 validation_errorque la nombra. No se deja el hueco: un agente leyendo{{monto}}en voz alta a su cliente es peor que un rechazo que usted ve en su log. - Sobra una que ninguna plantilla usa ⇒ la petición SE ACEPTA y se lo decimos. La respuesta trae
warnings.ignored_variablescon esas claves. Es así porque el texto del agente se edita en la consola y no puede tumbarle su integración cada vez que alguien reescribe una frase — pero tampoco puede quedarse callado, o usted daría por hecho que su variable actuó.
{ "request_id": "req_…", "job_id": "job_…", "usage": { "…": "…" }, "result": { "…": "…" },
"warnings": { "ignored_variables": ["descuento"] } }warnings no viene si no hay nada que avisar: la clave está ausente, no vacía. Trate su presencia como
la señal, no su contenido. Cuando viene, ignored_variables está siempre (vacía si no sobró ninguna) y, en
POST /v1/calls, puede venir además transfer_disabled (ver la operación, arriba). Qué trae el replay
de la clave depende de la superficie:
POST /v1/calls: los mismoswarningsque el acuse original, porque se guardan con el trabajo al admitir la llamada.- Turno del agente: se recalculan con el agente tal como está al repetir. Con las mismas
variablesy el mismo texto del agente salen iguales; si alguien editó sus plantillas en la consola entre los dos intentos,ignored_variablespuede cambiar.
metadata: sus identificadores, de ida y de vuelta
Es opaco para nosotros y para el agente: no entra en el prompt, el modelo no lo ve y nadie lo dice. Es el
sitio de su loan_id, su case_id o su número de expediente. Vuelve tal cual en el webhook del cierre,
dentro de data.metadata (página 11), y con eso cuadra el resultado contra su sistema
sin guardar nuestro job_id en ningún sitio.
Vuelve también cuando el trabajo vence sin cierre (job.expired), que es justo el caso en el que le hace
más falta.
Idempotencia: una clave identifica una operación, no una plantilla
Si repite una Idempotency-Key con otras variables, la petición se rechaza con
409 idempotency_key_reused en vez de devolverle el resultado de la primera. Reintentar la misma petición
—mismas variables— sigue siendo idempotente y le devuelve el mismo trabajo. Cambiar solo el metadata no
cuenta como otra operación: es un dato suyo de conciliación, no parte de lo que pidió.
Lo que el Gateway rellena y usted no puede escribir
En el turno, la identidad de su cuenta, la configuración del agente y la credencial con la que validamos las herramientas las pone el Gateway. Un cuerpo que intente escribir cualquiera de esas casillas se rechaza en vez de ignorarse: ignorarlo dejaría al integrador creyendo que configuró algo.
Elegir entre sus agentes es suyo. Configurarlos se hace en la consola, y es el producto.