Las dos APIs
Cómo funciona la plataforma (agentes, canales, conversaciones y motores) está en la introducción.
| API | Para qué | Key |
|---|---|---|
| Inferencia | Usar los motores en tu sistema: chat, transcripción y síntesis, compatible con el SDK de OpenAI. | llm, stt, tts |
| Plataforma | Lo mismo que el panel: agentes, llamadas, contactos, campañas, derivaciones, eventos y reportes. | calls |
Si preferís usar la plataforma desde un agente de IA, sin programar, mirá MCP para agentes de IA.
Empezar
- URL base:
https://app.atentina.com.ar/api/v1/inference - Autenticación:
Authorization: Bearer vaas_..., con una API key que tenga el alcance del motor. - Compatible con el SDK de OpenAI: cambiás la
base_urly la API key. El campomodeles obligatorio en los SDK pero se ignora: el modelo lo fija la plataforma.
- Creá tu cuenta con el plan Free (20 minutos por mes, llamadas web, panel y API).
- En el panel, entrá a API > Nueva API key, elegí el alcance y copiala: se muestra una sola vez.
- Hacé el primer pedido:
from openai import OpenAI
client = OpenAI(base_url="https://app.atentina.com.ar/api/v1/inference", api_key="vaas_...")
chat = client.chat.completions.create(
model="atentina", # obligatorio en el SDK, pero se ignora
messages=[{"role": "user", "content": "Hola"}],
)
print(chat.choices[0].message.content)API keys y alcances
Cada key hace solo lo que le diste. Una key puede tener varios alcances; conviene una por sistema y con el mínimo necesario, porque se revoca en el acto si se filtra.
| Alcance | Sirve para |
|---|---|
llm | POST /chat/completions |
stt | POST /audio/transcriptions |
tts | POST /audio/speech y GET /voices |
calls | La API de llamadas, agentes y reportes (no usa los motores) |
mcp | El servidor MCP, para conectar un agente de IA (ver /desarrolladores/docs/mcp); también la API de llamadas |
Las dos APIs no se cruzan: una key llm no administra agentes ni llamadas, y una key calls no usa los motores.
Endpoints
| Endpoint | Alcance | Qué hace |
|---|---|---|
POST /chat/completions | llm | Chat con el LLM, con o sin streaming |
POST /audio/transcriptions | stt | Transcribe un archivo de audio |
POST /audio/speech | tts | Sintetiza texto con una de las voces |
GET /voices | tts | Voces disponibles |
GET /models | cualquiera | Modelos que sirve la plataforma |
GET /usage | cualquiera | Tu consumo del mes contra el plan |
LLM
Acepta messages, max_tokens, temperature, top_p, stop, seed, response_format, tools, tool_choice y stream. La respuesta trae usage con los tokens de entrada y salida, que es lo que se descuenta.
curl https://app.atentina.com.ar/api/v1/inference/chat/completions \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hola, ¿qué horarios tienen?"}],"max_tokens":200}'Transcripción
Subís el archivo (wav, mp3, flac, ogg…; hasta 25 MB) y, si querés, language. Se descuentan los segundos de audio que informa usage.
curl https://app.atentina.com.ar/api/v1/inference/audio/transcriptions \
-H "Authorization: Bearer $ATENTINA_KEY" \
-F [email protected] -F language=esSíntesis
voice es obligatoria y tiene que ser una de GET /voices (41 voces con nombres argentinos, como sofia o martin). input admite hasta 1.500 caracteres. Se descuentan los segundos de audio generado.
curl https://app.atentina.com.ar/api/v1/inference/audio/speech \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"voice":"sofia","input":"Hola, ¿en qué te puedo ayudar?"}' \
--output respuesta.wavStreaming
El LLM y la síntesis se pueden consumir sin esperar a que termine la respuesta.
LLM: con "stream": true responde SSE como OpenAI: líneas data: {chunk}, un último chunk con usage y data: [DONE].
curl -N https://app.atentina.com.ar/api/v1/inference/chat/completions \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"stream": true, "max_tokens": 200, "messages": [{"role": "user", "content": "Contame algo breve"}]}'Síntesis: tres formas de recibir el audio.
| Pedido | Qué llega | Para qué |
|---|---|---|
response_format: "wav" (default) | El archivo WAV completo, al terminar | Guardar o reproducir un audio ya hecho |
response_format: "pcm" | Bytes PCM crudos (24 kHz, mono, 16 bits) a medida que se sintetizan | Reproducir en vivo o enviarlo a otro sistema |
response_format: "pcm" + stream_format: "sse" | Eventos SSE con el audio en base64 | Navegadores y clientes que ya consumen SSE |
# Escuchar mientras se sintetiza (ffplay viene con ffmpeg)
curl -sN https://app.atentina.com.ar/api/v1/inference/audio/speech \
-H "Authorization: Bearer $ATENTINA_KEY" -H "Content-Type: application/json" \
-d '{"voice":"sofia","input":"Hola, ¿en qué te puedo ayudar?","response_format":"pcm"}' \
| ffplay -f s16le -ar 24000 -ac 1 -nodisp -autoexit -En una medición del 9 de octubre de 2026, el primer audio en pcm llegó a los 53 ms, contra 2,1 s del WAV completo; y el primer token del LLM con stream, a los 32 ms.
Un stream cuenta contra el cupo igual que un pedido normal. Si cortás antes de que termine, se cobra solo lo ya entregado.
Límites y consumo
Los fija tu plan y valen para todas las keys de tu cuenta juntas. Los cupos son por mes calendario (se renuevan el día 1 a las 00:00, hora de Argentina) y están separados por motor: agotar uno no afecta a los otros.
| Límite | Qué mide |
|---|---|
api_llm_input_tokens | Tokens del prompt, por mes |
api_llm_output_tokens | Tokens generados, por mes |
api_tts_minutes | Audio sintetizado, en minutos por mes |
api_stt_minutes | Audio transcripto, en minutos por mes |
api_rate_limit | Pedidos por minuto, entre todas las keys |
- Antes de cada pedido se verifica la key, su alcance, el tope por minuto y el cupo. Si no pasa, el pedido no llega al motor ni se cobra.
- Un pedido que falla en el motor no se cobra.
- En el chat,
max_tokensse achica a lo que te queda de tokens de salida del mes. - La plataforma guarda cuánto consumiste, por key y por día, pero no los prompts, los audios ni los textos.
Para ver tu consumo del mes contra el plan (no gasta cupo):
curl https://app.atentina.com.ar/api/v1/inference/usage -H "Authorization: Bearer $ATENTINA_KEY"Errores
El cuerpo siempre tiene la misma forma:
{"detail": "texto para mostrar", "code": "codigo_estable", "errors": []}| HTTP | code | Cuándo |
|---|---|---|
| 401 | Sin API key, inválida o revocada | |
| 403 | scope_missing | La key no tiene el alcance de ese motor |
| 403 | not_in_plan | El plan no incluye ese motor |
| 429 | rate_limited | Te pasaste de los pedidos por minuto; trae Retry-After |
| 429 | api_* | Se agotó el cupo del mes de ese motor |
| 400 | upstream_rejected | El motor rechazó el pedido (prompt más largo que el contexto, audio ilegible) |
| 400 | payload_too_large | Audio de más de 25 MB: partilo |
| 422 | invalid_request / invalid_voice | Pedido mal armado, o voz inexistente |
| 502 | upstream_error | El motor no responde |
Un 429 por cupo agotado no se arregla reintentando: hay que esperar al día 1 o ampliar el plan. Uno por rate_limited sí, después de los segundos de Retry-After.
Qué incluye cada plan
La API de llamadas es la misma en todos los planes; lo que cambia es qué funciones están habilitadas y con qué tope. Si el plan no incluye algo, el pedido no hace nada y responde 429 con un code que dice qué falta.
| Función | Free | Mostrador | Sucursal | Red |
|---|---|---|---|---|
| Llamadas salientes (POST /calls) | No | Sí | Sí | Sí |
| No | Sí | Sí | Sí | |
| Contactos activos | No | 500 | 5.000 | Sin tope |
| Campañas de WhatsApp | No | No | Sí | Sí |
| Campañas de llamadas | No | No | Sí, de a 1 llamada | Sí, hasta 3 a la vez |
| Equipo para derivar llamadas | No | 1 persona | 2 personas | Sin tope |
Las llamadas salientes, las campañas de llamadas y las derivaciones usan los minutos salientes del plan y necesitan un número asignado a tu cuenta. Para saber qué tiene tu cuenta, con el motivo de lo que no:
curl https://app.atentina.com.ar/api/v1/plan/features -H "Authorization: Bearer $ATENTINA_KEY"{
"client_id": "…", "tier_name": "Sucursal", "free": false,
"features": {
"outbound_calls": {"enabled": true, "reason": null},
"campaigns_voice": {"enabled": true, "reason": null},
"campaigns_whatsapp": {"enabled": true, "reason": null},
"whatsapp": {"enabled": true, "reason": null},
"contacts": {"enabled": true, "reason": null},
"team": {"enabled": true, "reason": null},
"call_links": {"enabled": true, "reason": null}
}
}429 code | Cuándo |
|---|---|
outbound_minutes | Se acabaron los minutos salientes del mes (o el plan no tiene) |
concurrency_limit | Ya tenés todas las llamadas a la vez que permite el plan |
whatsapp_not_in_plan | El plan no incluye WhatsApp |
contacts_limit | La agenda está llena: archivá contactos para desarchivar otros. En una carga, los que no entran vuelven en invalid con el motivo |
campaign_channel_not_in_plan | El plan no incluye campañas por ese canal |
campaign_concurrency | concurrency pasa el tope del plan |
campaign_contacts | La campaña tiene más contactos de los que permite el plan |
campaigns_per_month | Ya iniciaste todas las campañas del mes |
campaign_followup_not_in_plan | El plan no incluye el seguimiento por WhatsApp |
team_members_limit | El equipo está completo, o el plan no incluye derivación |
Agentes y llamadas
Con una key calls administrás agentes, lanzás y cortás llamadas, cargás contactos, armás campañas, configurás las derivaciones a tu equipo, leés los eventos y descargás reportes, con la URL base https://app.atentina.com.ar/api/v1. También se puede hacer desde tu agente de programación, con las skills para Claude Code, Cursor y Codex. La referencia completa de cada pedido y respuesta está en el panel, en /api/v1/docs.
Una llamada
El agente llama al número y la conversación queda en el panel. El teléfono va en formato internacional; from_number_id elige con qué número tuyo sale (sin él, el primero) y voice cambia la voz.
curl https://app.atentina.com.ar/api/v1/calls \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"agent_id": "<id del agente>", "phone": "+5493515551234"}'La respuesta trae el conversation_id. Con GET /calls/{conversation_id} ves el estado (pendiente, sonando, en_curso, finalizada, fallida), la duración, los datos que tomó el agente y el resultado. Para muchos números, usá una campaña.
Tu propio LLM
Por defecto, el agente piensa con nuestro modelo de lenguaje. Si querés otro, cargás la API key de tu proveedor y elegís desde qué turno de la conversación lo usa: por ejemplo, el nuestro para saludar y tomar los datos, y el tuyo desde el cuarto mensaje.
Proveedores
- Compatible con OpenAI: OpenAI, Groq, Together, DeepSeek o cualquier API con el formato de OpenAI, con su URL (https y pública).
- OpenRouter y Anthropic, con su URL fija.
Cómo se configura
- La conexión. En el panel, Configuración > Modelos > Nueva conexión: el proveedor, tu API key y el modelo. Al guardarla se prueba sola: autentica y mide cuánto tarda en responder. La key queda cifrada y no se vuelve a mostrar; se ven sus últimos 4 caracteres.
- El agente. En su editor, sección Modelo de lenguaje, o por API en su definición (
PUT /agents/{id}/definition, que guarda una versión nueva): hasta 3 etapas, cada una desde un turno. La etapa sinconnectiones nuestro modelo. Un turno es cada mensaje de la persona; el saludo del agente no cuenta.
"llm": {
"stages": [
{"from_turn": 1},
{"from_turn": 4, "connection": "<id de la conexión>", "model": "<modelo del proveedor>"}
],
"fallback_local": true
}- Respaldo: con
fallback_local, si tu proveedor falla o tarda más de 20 segundos, ese turno lo contesta nuestro modelo y la conversación sigue. Sin respaldo, el turno falla. - Costos: los tokens los pagás a tu proveedor. Los minutos del plan se cuentan igual.
- Latencia: se suma lo que tarda tu proveedor en empezar a responder. En el detalle de cada conversación se ve qué proveedor y modelo contestó cada turno.
- Privacidad: desde el turno en que lo usa, el texto de la conversación y las instrucciones del agente van a tu proveedor, bajo sus condiciones. El audio no sale: la voz y la transcripción siguen en nuestros servidores. Ver la política de privacidad.
La API de inferencia sigue siendo solo nuestra: tu proveedor lo usa el agente, no esa API.
Cortar y bloquear
Para terminar una llamada en curso desde tu sistema: cuelga a la otra punta y la llamada queda con ended_reason hangup_by_user. Si la llamada ya terminó, responde 409 call_not_active.
curl -X POST https://app.atentina.com.ar/api/v1/calls/<conversation_id>/hangup -H "Authorization: Bearer $ATENTINA_KEY"
# {"status": "finalizada", "ended_reason": "hangup_by_user"}Para no volver a atender un número, bloquealo: sus llamadas entrantes, por tu línea o por WhatsApp, se cortan sin atender y quedan como rechazada con ended_reason blocked_number. GET /blocked-numbers los lista y DELETE /blocked-numbers/{phone} desbloquea uno. Si ya estaba bloqueado, se actualiza la nota.
curl https://app.atentina.com.ar/api/v1/blocked-numbers \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"phone": "+5493515551234", "note": "Llama para insultar"}'Contactos
Cada cuenta tiene su agenda. El teléfono es lo único obligatorio y no se repite: cargar uno que ya está lo actualiza (los datos se suman, no se borran). El resto es opcional: name, email, external_id (el id en tu sistema, para cruzar el reporte), attributes (datos libres) y tags (para armar campañas por grupo).
curl https://app.atentina.com.ar/api/v1/contacts \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '[{"phone": "351 555-1234", "name": "Ana Pérez", "external_id": "C-1032",
"attributes": {"deuda": 15000, "vence": "10 de noviembre"}, "tags": ["mora_30"]},
{"phone": "+54 9 11 5555-0000"}]'Por CSV, con POST /contacts/import: una columna de teléfono (telefono, celular, phone…) y, si querés, nombre, email, id y etiquetas; el resto de las columnas pasan a attributes. La respuesta dice cuántos se crearon y actualizaron, y cuáles no entraron, con la fila y el motivo.
telefono,nombre,deuda,vence
351 555-1234,Ana Pérez,15000,10 de noviembre
11 5555-0000,,8200,15 de noviembre- No se borran: se archivan (
POST /contacts/{id}/archiveo en lote por ids o etiquetas). Un archivado no entra en campañas nuevas y sale de las que están en curso.GET /contacts?archived=truelos lista. Si lo volvés a cargar, se actualiza pero sigue archivado hasta que lo desarchives. - No contactar:
/contacts/optoutsguarda los teléfonos que pidieron no ser contactados, por canal (voice,whatsappoall). Ninguna campaña los llama ni les escribe. También se cargan solos: cuando alguien responde "baja" por WhatsApp, o lo pide en la llamada si el agente tiene un resultado marcado como "no contactar".
Campañas
Una campaña llama ("channel": "voice") o escribe por WhatsApp ("channel": "whatsapp", con una plantilla aprobada) a una lista de contactos, sola, dentro del horario que le pongas. Este ejemplo llama a los contactos con la etiqueta mora_30, de a tres a la vez, de lunes a viernes de 9 a 20, desde el 3 de noviembre, con hasta tres intentos cada dos horas:
curl https://app.atentina.com.ar/api/v1/campaigns \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "voice",
"name": "Mora 30 días - noviembre",
"agent_id": "<id del agente>",
"purpose": "cobranza",
"tags": ["mora_30"],
"concurrency": 3,
"window_start": 9, "window_end": 20, "weekdays": [0, 1, 2, 3, 4],
"starts_at": "2026-11-03T12:00:00Z",
"max_attempts": 3, "retry_minutes": 120,
"callback_url": "https://tu-sistema.com.ar/atentina/webhook",
"start": true
}'- De a uno o en paralelo:
concurrencyen 1 llama de a un contacto; con N, hasta N a la vez (el tope lo pone tu plan).rate_per_minutelimita cuántas llamadas empiezan por minuto. - Programada:
starts_atyends_at(UTC), y el horario en la hora de Argentina conwindow_start,window_endyweekdays(0 es lunes). - Reintentos:
max_attempts,retry_minutesyretry_on, los resultados que se reintentan (por defecto no atendió, ocupado, contestador y falla). - Datos del contacto: el saludo del agente puede usar
{{nombre}}o cualquier atributo ({{deuda}}). Un atributo con el mismo nombre que un dato que el agente pide ya no se pregunta, y el resto lo recibe como contexto. - Finalidad:
purposeescomercial,cobranza,turnosoaviso. Una comercial se inicia con"no_call_registry_checked": true: confirmás que filtraste la lista contra el Registro Nacional No Llame (Ley 26.951). - Seguimiento por WhatsApp: con
followup(apagado por defecto), a quien no atendió después del último intento le llega una plantilla de WhatsApp; si responde, sigue la conversación con el agente.
| Endpoint | Qué hace |
|---|---|
POST /campaigns | Crea la campaña (en draft) con sus contactos; con "start": true la inicia en el mismo pedido |
POST /campaigns/{id}/contacts | Suma contactos: contact_ids, tags, contacts (lista) o csv |
POST /campaigns/{id}/start | La inicia (o la programa, si tiene starts_at) |
POST /campaigns/{id}/pause · /resume · /cancel | Las llamadas en curso terminan normalmente |
PATCH /campaigns/{id} | Cambia horario, ritmo, reintentos o webhook de una campaña que no terminó |
GET /campaigns | Lista, con filtros channel y status |
GET /campaigns/{id} | Estado y avance: lo que se consulta en el polling |
GET /campaigns/{id}/contacts | Contactos paginados, con filtros status y result, y su conversation_id |
GET /campaigns/{id}/stats | Indicadores: tasa de contacto y de objetivo, duración media, atendidas por hora |
GET /campaigns/{id}/report.csv | Reporte con una fila por contacto |
GET /campaigns/plan | Qué permite tu plan: canales, campañas por mes, contactos por campaña, llamadas a la vez |
Estado y avance
Consultá GET /campaigns/{id} cada pocos segundos (el panel lo hace cada 5). progress cuenta los contactos por estado y results, por resultado de la llamada; goal son los que llegaron al objetivo del agente (por ejemplo, una promesa de pago), y eta_minutes estima cuánto falta al ritmo de los últimos 15 minutos. En WhatsApp, results trae enviados, entregados, leídos y respondidos.
curl https://app.atentina.com.ar/api/v1/campaigns/<id> -H "Authorization: Bearer $ATENTINA_KEY"{
"id": "…", "channel": "voice", "status": "running", "status_reason": null, "in_window": true,
"total": 500, "percent": 33.0, "eta_minutes": 95,
"progress": {"pending": 310, "in_progress": 3, "retry": 22, "done": 140, "failed": 25, "skipped": 0},
"results": {"answered": 120, "no_answer": 30, "busy": 8, "voicemail": 10,
"invalid": 6, "rejected": 1, "failed": 0},
"goal": 64, "minutes_used": 212.5
}| Estado | Qué significa |
|---|---|
draft | Creada, sin iniciar. Podés sumar contactos y cambiar la configuración. |
scheduled | Iniciada con fecha de inicio futura: arranca sola en starts_at. |
running | Llamando o enviando, dentro del horario. Fuera de horario sigue running con in_window en false. |
paused | Pausada por vos, o sola: se acabaron los minutos del plan o fallaron muchas llamadas seguidas (status_reason dice por qué). |
done | Terminó: no queda nadie por contactar, o pasó ends_at. |
cancelled | Cancelada. Los contactos pendientes quedan en skipped. |
| Resultado | Qué pasó | Se reintenta |
|---|---|---|
answered | Atendió y habló con el agente | No |
no_answer | Sonó y no atendió | Sí |
busy | Ocupado | Sí |
voicemail | Atendió un contestador | Sí |
invalid | El número no existe | No |
rejected | Rechazó la llamada | No |
failed | Falló la llamada por otro motivo | Sí |
Reporte
Una fila por contacto: external_id, teléfono, nombre, sus datos, estado, resultado, intentos, duración, resultado de la conversación, si llegó al objetivo, los datos que tomó el agente y el link a la conversación. Se abre en Excel con los acentos bien.
curl https://app.atentina.com.ar/api/v1/campaigns/<id>/report.csv -H "Authorization: Bearer $ATENTINA_KEY" -o reporte.csvLímites
Las campañas son parte del plan: qué canales, cuántas campañas por mes, cuántos contactos por campaña, cuántas llamadas a la vez y el tamaño de la agenda (GET /campaigns/plan). Las llamadas de una campaña usan los minutos salientes del plan; si se acaban, la campaña se pausa sola y la retomás con /resume cuando haya minutos.
Webhook
En vez de consultar el estado, podés recibirlo: con callback_url (https), la campaña hace un POST a tu sistema por cada contacto que termina (contact.finished, con la misma fila del reporte), por cada seguimiento por WhatsApp (contact.followup) y uno al terminar (campaign.finished, con el avance y los indicadores).
- Firma: cada POST trae
X-Atentina-Signature: sha256=<hex>, el HMAC-SHA256 del cuerpo con tucallback_secret. Si no mandás uno al crear la campaña, se genera y se muestra una sola vez en la respuesta. - Reintentos: se toma como recibido cualquier 2xx. Si no, se reintenta a los 30 s, 2 min, 10 min, 1 h y 6 h.
delivery_id(también enX-Atentina-Delivery) es el mismo en los reintentos: usalo para no procesar dos veces el mismo evento.
import hashlib, hmac
def firma_valida(cuerpo: bytes, header: str, secreto: str) -> bool:
esperada = "sha256=" + hmac.new(secreto.encode(), cuerpo, hashlib.sha256).hexdigest()
return hmac.compare_digest(esperada, header)
# header = request.headers["X-Atentina-Signature"]; cuerpo = el body crudo, antes de parsear el JSONEquipo y derivaciones
El agente puede pasar una llamada a una persona de tu equipo. Mientras el cliente escucha música, llama a la persona, le cuenta en pocas palabras de qué se trata, y si marca 1 los conecta. La conversación con la persona también queda transcripta en el panel. Se arma en tres pasos.
1. Las personas. Cada una con su teléfono de Argentina, al que se la llama. Cuántas podés cargar depende del plan.
curl https://app.atentina.com.ar/api/v1/team \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Laura Gómez", "phone": "351 555-4321"}'2. Las áreas. Un área agrupa a las personas que atienden un tema. description es lo que lee el agente para elegirla. strategy en sequential las llama en el orden de member_ids, y en round_robin va rotando quién arranca. Cada una suena ring_seconds. Con schedule (hora de Argentina, franjas por día y feriados en closed), fuera de horario el agente no deriva y toma el mensaje; sin horario, atiende siempre.
curl https://app.atentina.com.ar/api/v1/handoff-areas \
-H "Authorization: Bearer $ATENTINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Ventas",
"description": "Presupuestos, precios y compras",
"member_ids": ["<id de Laura>", "<id de otra persona>"],
"strategy": "sequential",
"ring_seconds": 20,
"schedule": {"week": {"mon": [["09:00", "13:00"], ["16:00", "20:00"]],
"tue": [["09:00", "18:00"]]},
"closed": ["2026-12-25"]}
}'3. El agente. En su definición (PUT /agents/{id}/definition, que guarda una versión nueva) van las áreas a las que puede derivar y, en when, cuándo hacerlo en palabras (vacío: la descripción del área). Por ahora solo con el motor classic.
"handoff": {
"areas": [
{"area_id": "<id del área Ventas>", "when": "Quiere un presupuesto o pide hablar con un vendedor"}
],
"hold_music": {"enabled": true, "track": "default", "volume": 0.3}
}- Si nadie atiende (no atiende, da ocupado, salta un contestador o no marca 1), el agente vuelve con el cliente y le toma el mensaje.
- Volver al agente: la persona marca
return_digits(2 por defecto; nunca con*) y el agente retoma la llamada. Si la persona corta,on_person_hangupelige entre que el agente siga (resume) o se despida y corte (end). - Minutos: el tramo con la persona gasta minutos salientes desde que atiende; los intentos sin atender no se cobran. Sin minutos salientes, no se deriva. La llamada transferida ocupa una sola de las llamadas a la vez de tu plan.
- Dónde no hay derivación: en las campañas y en los mensajes de WhatsApp. Sí en las entrantes por tu línea o por WhatsApp, y en las salientes que lanzás con
POST /calls.
| Endpoint | Qué hace |
|---|---|
GET · POST /team | Lista o suma personas: name, phone (para transferirle llamadas) y active |
PATCH · DELETE /team/{id} | Cambia o saca a una persona |
GET · POST /handoff-areas | Lista o crea áreas; cada una trae open_now y su horario en palabras (schedule_text) |
GET · PATCH · DELETE /handoff-areas/{id} | Un área. Si un agente la usa no se borra (409 area_in_use) |
GET /handoff-areas/defaults | Los textos que dice el agente al derivar, para cambiarlos por área en messages |
GET /hold-music | Los temas de espera para hold_music.track |
Eventos
Cada llamada deja una línea de tiempo: cuándo sonó, cuándo la atendieron, cada intento de derivación y cómo terminó. GET /events la devuelve en orden de llegada, con filtros conversation_id, type (exacto o prefijo, como handoff.), since y until. Para leer solo lo nuevo, repetí el pedido con after_id igual al último id que recibiste (hasta 1.000 por pedido con limit).
curl "https://app.atentina.com.ar/api/v1/events?type=handoff.&after_id=1520" -H "Authorization: Bearer $ATENTINA_KEY"[
{"id": 1521, "at": "2026-11-03T14:02:11Z", "type": "handoff.requested", "actor": "agent",
"conversation_id": "…", "area_id": "…", "team_member_id": null, "data": {}},
{"id": 1522, "at": "2026-11-03T14:02:12Z", "type": "handoff.dialing", "actor": "system",
"conversation_id": "…", "area_id": "…", "team_member_id": "…", "data": {}},
{"id": 1525, "at": "2026-11-03T14:02:31Z", "type": "handoff.connected", "actor": "person",
"conversation_id": "…", "area_id": "…", "team_member_id": "…", "data": {}}
]| Tipo | Qué pasó |
|---|---|
call.ringing · call.answered · call.ended | La llamada suena, la atienden y termina (call.ended trae el estado final y el motivo) |
handoff.requested | El agente decidió pasar la llamada a un área |
handoff.dialing · answered · confirmed | Se llama a una persona, atiende y marca 1 para tomar la llamada |
handoff.no_answer · busy · failed · machine · not_confirmed | Ese intento no salió: no atendió, ocupado, falla, contestador o no marcó 1 |
handoff.connected · ended | La persona y el cliente hablan; ended trae los segundos |
handoff.returned_to_ai · person_hung_up | La persona devolvió la llamada al agente o cortó |
handoff.unavailable | No se pudo derivar: fuera de horario, sin personas, sin minutos o sin canal (data trae el motivo) |
hold.started · hold.ended | Empieza y termina la música de espera |