Saltar al contenido principal

Llamadas

La API de Llamadas te permite listar el historial de llamadas, ver detalles de llamadas con transcripciones, descargar grabaciones, iniciar llamadas salientes y ejecutar campanas de llamadas por lotes.

Cada endpoint de esta seccion acepta ambos metodos de autenticacion: JWT Bearer y clave de API (X-API-Key). Esto significa que una integracion automatizada (por ejemplo, un manejador que recibe el webhook de fin de llamada) puede listar llamadas y descargar grabaciones usando solo una clave de API.

Listar Llamadas

GET /calls

Devuelve una lista paginada de registros de llamadas, ordenados por hora de inicio (mas recientes primero).

Parametros de Consulta

ParametroTipoPor defectoDescripcion
pageinteger1Numero de pagina
page_sizeinteger20Elementos por pagina (maximo 100)
directionstring--Filtrar por inbound o outbound
statusstring--Filtrar por estado de llamada (ej. completed, failed) o resultado (ej. voicemail, caller_hangup, answered_no_session)
searchstring--Buscar por numero de telefono (origen o destino)

Respuesta

{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"call_sid": "CA1234567890abcdef",
"direction": "inbound",
"from_number": "+15551234567",
"to_number": "+15559876543",
"start_time": "2026-03-01T14:30:00Z",
"end_time": "2026-03-01T14:32:45Z",
"duration": 165.0,
"status": "completed",
"outcome": "completed",
"error_reason": null,
"created_at": "2026-03-01T14:30:00Z",
"agent_name": "Support Agent"
}
],
"total": 142,
"page": 1,
"page_size": 20
}

Campos del Registro de Llamada

CampoTipoDescripcion
iduuidID interno del registro de llamada
call_sidstringTwilio Call SID
directionstringinbound o outbound
from_numberstringNumero de telefono del llamante
to_numberstringNumero de telefono llamado
start_timedatetimeMarca de tiempo de inicio de llamada
end_timedatetime | nullMarca de tiempo de fin de llamada
durationfloat | nullDuracion de la llamada en segundos. Se concilia con el audio grabado, de modo que no puede superar con mucho el audio que la llamada realmente transporto (ver mas abajo).
statusstringEstado de la llamada (completed, failed, in-progress, queued)
outcomestring | nullResultado de la llamada (completed, caller_hangup, no_answer, timeout, transferred, voicemail, invalid_number, answered_no_session, failed). timeout indica que la llamada la terminamos nosotros y no el llamante: un límite de silencio, un límite de duración máxima, o una línea que dejó de transmitir audio; caller_hangup es una desconexión real del llamante; voicemail indica que contestó un contestador automático, ya sea porque lo detectó la detección de contestador o porque el agente reconoció el mensaje; answered_no_session indica que la llamada fue contestada y facturada pero no se registró ninguna conversación y se desconoce el motivo (ver más abajo).
error_reasonstring | nullDescripcion del error si la llamada fallo
created_atdatetimeMarca de tiempo de creacion del registro
agent_namestring | nullNombre del agente que atendio la llamada

Llamadas Contestadas Sin Conversacion

outcome: "answered_no_session" marca una llamada que el proveedor de telefonia contesto y finalizo con normalidad, pero para la que el pipeline de voz nunca registro una sesion. El registro lleva una duration, start_time y end_time reales, y status: "completed", mientras que transcript, recording_url y call_analysis quedan en null porque no se capturo nada. error_reason lo explica en lenguaje claro.

Tratala como un caso propio en lugar de mezclarla con una llamada sin respuesta:

  • No es no_answer ni busy. Esos significan que la llamada nunca conecto. Esta si conecto, y la persona al otro lado escucho lo que la linea transmitiera.
  • Tampoco es voicemail. Una llamada que contesto un contestador automatico se etiqueta como voicemail, porque algo identifico la maquina. answered_no_session es lo que queda cuando nada lo hizo, asi que el motivo de la conversacion ausente se desconoce.
  • Rellamar es una decision tuya. Puede que ya se haya hablado con el destinatario, y no hay transcripcion para comprobarlo.
  • El webhook call.completed se dispara para ella como para cualquier otra llamada terminal, con el mismo outcome y con transcript y recording_url en null.

Relacion entre duration y la Grabacion

duration mide la llamada desde que se abrio la sesion hasta que se cerro, por lo que normalmente es uno o dos segundos mas larga que la grabacion: el establecimiento y el cierre quedan a ambos lados del audio.

De vez en cuando una linea deja de transportar audio sin llegar a avisar de que la llamada ha terminado. Cuando eso ocurre cerramos la sesion poco despues de que el audio se detenga, el resultado es timeout, y duration se concilia con la grabacion para que el registro - y el consumo que alimenta - refleje el audio que la llamada realmente transporto y no el tiempo que la linea permanecio abierta. Las llamadas cuya duration ya coincide con su grabacion nunca se ajustan.

Obtener Detalle de Llamada

GET /calls/{call_id}

Devuelve una llamada individual con detalles completos incluyendo la transcripcion de la conversacion y metricas de uso de proveedores.

Respuesta

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"call_sid": "CA1234567890abcdef",
"direction": "inbound",
"from_number": "+15551234567",
"to_number": "+15559876543",
"start_time": "2026-03-01T14:30:00Z",
"end_time": "2026-03-01T14:32:45Z",
"duration": 165.0,
"status": "completed",
"outcome": "completed",
"error_reason": null,
"created_at": "2026-03-01T14:30:00Z",
"agent_name": "Support Agent",
"transcript": [
{
"role": "user",
"content": "Hi, I need help with my order.",
"timestamp": "2026-03-01T14:30:08Z"
},
{
"role": "assistant",
"content": "Of course! Could you share your order number?",
"timestamp": "2026-03-01T14:30:11Z"
}
],
"provider_usage": {
"deepgram": { "type": "stt", "model": "nova-2" },
"openai": { "type": "llm", "model": "gpt-4o" },
"elevenlabs": { "type": "tts", "model": "eleven_turbo_v2" }
},
"recording_url": "call-CA1234567890abcdef.wav"
}

Campos Adicionales de Detalle

CampoTipoDescripcion
transcriptarray | nullTurnos de conversacion con role, content y timestamp, mas interrupted en un turno que la persona que llama corto (ver abajo)
provider_usageobject | nullNombres de proveedores y modelos utilizados durante la llamada
recording_urlstring | nullNombre del archivo de grabacion WAV (si la grabacion estaba habilitada)

Marcas de Tiempo de los Turnos

Cada turno lleva el momento en que ocurrio, no el momento en que se escribio el registro, de modo que una transcripcion puede superponerse a la grabacion y reconstruir la llamada turno a turno.

Turnos Interrumpidos

Cuando la persona que llama habla por encima del agente y el agente se detiene a mitad de frase, ese turno registra unicamente el texto que la persona alcanzo a oir. Lleva "interrupted": true y su content termina con [cut off by caller]. El turno de la persona que interrumpio se prefija con [interrupting]:

"transcript": [
{ "role": "user", "content": "Can you book me a table?" },
{
"role": "assistant",
"content": "Sure, I can book that for [cut off by caller]",
"interrupted": true
},
{ "role": "user", "content": "[interrupting] actually make it Friday" },
{ "role": "assistant", "content": "Friday it is. What time suits you?" }
]

La clave interrupted solo aparece en los turnos que fueron cortados, asi que tratala como opcional. De este modo la transcripcion de un turno interrumpido coincide con la grabacion de audio, en lugar de mostrar la respuesta mas larga que el agente habia generado.

El punto de corte es lo que realmente habia llegado a la persona que llama en el momento de interrumpir. El audio del agente sigue reproduciendose un instante despues de componer la respuesta, asi que un turno interrumpido en esa ventana tambien lleva "interrupted": true y se trunca en los mismos terminos.

Descargar Grabacion

GET /calls/{call_id}/recording

Descarga el archivo de grabacion WAV de una llamada. Devuelve 404 si no hay grabacion disponible.

El webhook call.completed incluye un recording_download_url que apunta a este endpoint. Descargalo con tu clave de API en la cabecera X-API-Key para obtener el audio.

Respuesta

200 OK con Content-Type: audio/wav y el contenido binario del archivo.

Iniciar Llamada Saliente

POST /calls

Encola una llamada saliente individual. La llamada se procesa de forma asincrona a 1 llamada por segundo por un worker en segundo plano. La respuesta se devuelve inmediatamente con un call_id y un call_sid provisional que se actualiza cuando Twilio asigna el SID real.

Este endpoint acepta autenticacion tanto por Bearer JWT como por API key.

Cuerpo de la Solicitud

{
"to_number": "+15551234567",
"from_number": "+15559876543",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"variables": {
"customer_name": "Jane Doe",
"company_name": "Globex"
}
}

Campos de la Solicitud

CampoTipoRequeridoDescripcion
to_numberstringSiNumero de telefono de destino en formato E.164
from_numberstringNoNumero de identificacion del llamante. Si se omite, se resuelve desde el numero asignado al agente o el valor predeterminado de la configuracion SIP.
agent_idstringNoUUID del agente que manejara la llamada. Si se omite, se usa el agente asignado a from_number.
variablesobjectNoSustituciones por llamada para las variables personalizadas del agente objetivo, como un mapa de nombre de variable a valor de texto. Se aplica solo a esta llamada y nunca cambia los valores predeterminados guardados del agente. Cada clave debe ser una variable personalizada definida en el agente objetivo; el nombre de una variable integrada o una clave desconocida devuelve 422 con las claves ofensivas.
Validación estricta (a diferencia del panel)

A diferencia del editor del panel, que solo advierte sobre marcadores no reconocidos y aun así te deja guardar, este endpoint valida variables de forma estricta. Una clave que coincide con el nombre de una variable integrada (las integradas nunca son sustituibles) o que no es una variable personalizada definida en el agente objetivo se rechaza con 422, y la respuesta lista las claves ofensivas. Esto expone los errores de integración de forma explícita en lugar de descartar datos silenciosamente. Consulta Variables Dinámicas para el modelo de variables.

Respuesta

202 Accepted

{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_sid": "queued-550e8400e29b",
"status": "queued",
"deduplicated": false,
"dedupe_reason": null
}

El call_sid sera un valor provisional con prefijo queued- hasta que el worker en segundo plano inicie la llamada con Twilio.

Encabezados de la Solicitud

EncabezadoRequeridoDescripcion
Idempotency-KeyNoTu propio identificador para este envio de llamada (maximo 255 caracteres). Repetir la misma clave devuelve la llamada original en lugar de marcar de nuevo. Consulta Supresion de Duplicados.

Supresion de Duplicados

Encolar una llamada es "dispara y olvida", asi que una automatizacion que reintenta -- o que lanza una rafaga de disparos antes de que su propio registro de "ya llamado" se haya guardado -- podria llamar a la misma persona una vez por solicitud. Dos protecciones se aplican antes de la cola, y ambas son seguras ante rafagas de solicitudes simultaneas: solo una puede ganar, sin importar cuantas lleguen en el mismo instante.

Clave de idempotencia. Envia un encabezado Idempotency-Key con tu propio identificador del envio (un id de ejecucion del flujo, un id de fila, un UUID -- cualquier valor estable para esa llamada logica). La primera solicitud encola la llamada y vincula la clave a ella durante 24 horas. Cualquier repeticion de la misma clave dentro de esa ventana no encola nada y devuelve el call_id y el call_sid de la llamada original. La clave tiene alcance por cuenta y por endpoint, asi que el mismo valor puede reutilizarse de forma independiente en POST /calls y en POST /calls/batch. Solo se compara la clave, nunca el cuerpo de la solicitud.

Periodo de espera por numero. Incluso sin clave, una segunda llamada a un numero al que acabas de llamar se suprime durante un periodo de espera corto (60 segundos por defecto, configurable por despliegue y desactivable). Es una red de seguridad frente a tormentas de reintentos; no sustituye al envio de una clave de idempotencia.

Una solicitud suprimida no es un error. Devuelve 202 Accepted con los identificadores de la llamada ya encolada, mas:

CampoTipoDescripcion
deduplicatedbooleantrue cuando esta solicitud no encolo nada
statusstringduplicate en lugar de queued
dedupe_reasonstring | nullidempotency_key o number_cooldown
{
"call_id": "550e8400-e29b-41d4-a716-446655440000",
"call_sid": "queued-550e8400e29b",
"status": "duplicate",
"deduplicated": true,
"dedupe_reason": "idempotency_key"
}

Como la respuesta lleva el call_id original, un cliente que reintenta tras un tiempo de espera puede tratar con seguridad una respuesta duplicate como exito y seguir consultando la misma llamada. Vigila deduplicated en tu integracion: un flujo constante de true significa que tu automatizacion esta enviando mas disparos de los que pretende.

La primera llamada a cualquier numero, con o sin encabezado, se comporta exactamente igual que siempre.

Llamadas Salientes por Lotes

POST /calls/batch

Encola multiples llamadas salientes a la vez. Todas las llamadas comparten el mismo from_number y agent_id, y se procesan a 1 llamada por segundo.

Cuerpo de la Solicitud

{
"to_numbers": ["+15551111111", "+15552222222", "+15553333333"],
"from_number": "+15559876543",
"agent_id": "550e8400-e29b-41d4-a716-446655440000"
}

Campos de la Solicitud

CampoTipoRequeridoDescripcion
to_numbersarray[string]SiLista de numeros de telefono de destino
from_numberstringNoIdentificacion de llamante compartida para todas las llamadas
agent_idstringNoAgente que manejara todas las llamadas del lote

Encabezados de la Solicitud

EncabezadoRequeridoDescripcion
Idempotency-KeyNoTu propio identificador para este envio por lotes (maximo 255 caracteres). Repetir la misma clave devuelve el batch_id original y no encola nada.

Respuesta

202 Accepted

{
"batch_id": "batch-abc123",
"total": 3,
"status": "processing",
"deduplicated": false,
"dedupe_reason": null,
"skipped_numbers": []
}

total es el numero de llamadas que esta solicitud encolo, que no siempre coincide con la longitud de to_numbers.

Supresion de Duplicados en un Lote

La supresion de duplicados tambien se aplica a los lotes, con una diferencia: el periodo de espera por numero se evalua numero a numero, asi que una lista con solapamiento sigue enviando los numeros nuevos.

  • Idempotency-Key repetida: no se encola nada. La respuesta lleva el batch_id original, total: 0, status: "duplicate" y todos los numeros solicitados en skipped_numbers.
  • Numeros en periodo de espera: el resto de la lista se encola con normalidad. total cuenta solo esos, los numeros suprimidos se listan en skipped_numbers, y deduplicated es true con dedupe_reason: "number_cooldown". Un numero repetido dos veces dentro del mismo cuerpo se marca una sola vez por el mismo motivo.
  • Todos los numeros en periodo de espera: no se encola nada y status es duplicate.
{
"batch_id": "batch-def456",
"total": 2,
"status": "processing",
"deduplicated": true,
"dedupe_reason": "number_cooldown",
"skipped_numbers": ["+15551111111"]
}

Obtener Estado del Lote

GET /calls/batch/{batch_id}

Consulta el progreso de un trabajo de llamadas por lotes.

Respuesta

{
"batch_id": "batch-abc123",
"total": 3,
"queued": 1,
"initiated": 1,
"failed": 1,
"calls": [
{ "to_number": "+15551111111", "status": "initiated", "call_sid": "CA..." },
{ "to_number": "+15552222222", "status": "queued", "call_sid": null },
{ "to_number": "+15553333333", "status": "failed", "call_sid": null, "error": "Invalid number" }
]
}