Voice Automation Platform

NeuraVoz API

REST + webhooks para construir agentes de voz IA conectados a tu negocio. Eventos reales de tu empresa activan llamadas con variables dinámicas, contexto y conversación natural.

Introducción

Base URL: https://neuravoice.tech/v1

Todas las respuestas son JSON. Todos los timestamps son ISO 8601 UTC. Todos los números E.164 (+12025550123).

El SDK oficial llegará en fase 2. Por ahora cualquier cliente HTTP funciona — los ejemplos usan curl.

¿No sos developer?

Mirá la guía paso a paso para usar todo desde el dashboard sin escribir código.

Quickstart

Tres pasos para tener una llamada IA andando:

  1. Creá una API key en /dashboard/developers.
  2. Creá un agente vía dashboard o API (POST /v1/agents).
  3. Disparale una llamada:
curl -X POST https://neuravoice.tech/v1/calls \
  -H "Authorization: Bearer nvk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "agent_id": "TU_AGENT_ID",
    "from": "+18336528039",
    "to": "+5491155551234",
    "dynamic_variables": { "nombre": "Carlos" }
  }'

SignalWire llama al to. Cuando contesta, el agente conversa. Vos recibís webhooks con cada cambio de estado.

Autenticación

Las API keys se crean en /dashboard/developers. Cada key empieza con nvk_live_ y se muestra una sola vez al crearse — guárdala segura.

Pásala en cada request como Authorization: Bearer ...:

curl https://neuravoice.tech/v1/agents \
  -H "Authorization: Bearer nvk_live_abc123..."

Las keys se hashean (SHA-256) antes de almacenarse. No guardamos plaintext en ninguna parte después del primer reveal.

Agentes

Un agente es una configuración (nombre, prompt, voz, idioma) gestionada por nuestro motor de IA conversacional. Cuando lo conectás a una llamada, el agente conversa en tiempo real con el destinatario.

Listar agentes

GET /v1/agents
Authorization: Bearer nvk_live_...

# Respuesta
{
  "object": "list",
  "data": [
    {
      "id": "uuid",
      "name": "Recepcionista Clínica",
      "prompt": "Eres una recepcionista...",
      "first_message": "Hola, soy María de Clínica Vista...",
      "voice_id": "EXAVITQu4vr4xnSDxMaL",
      "language": "es",
      "provider_agent_id": "agent_xxx",
      "created_at": "2026-05-20T...",
      "updated_at": "2026-05-20T..."
    }
  ]
}

Crear agente

POST /v1/agents
Content-Type: application/json

{
  "name": "Recordatorios Dental",
  "prompt": "Eres una asistente de Clínica Dental. Recuerda al paciente su cita de mañana. Sé breve y amable. Si quiere reprogramar, dile que se contactará un humano.",
  "first_message": "Hola {{nombre}}, te llamo de Clínica Dental para confirmar tu cita.",
  "voice_id": "EXAVITQu4vr4xnSDxMaL",
  "language": "es"
}

# Respuesta 201
{
  "id": "uuid",
  "name": "Recordatorios Dental",
  ...
}

El campo first_message y prompt pueden usar variables como {{nombre}} — se reemplazan al iniciar cada llamada con los valores que pases en dynamic_variables.

Actualizar / Eliminar

PATCH /v1/agents/{id}    # mismo body que POST
DELETE /v1/agents/{id}

Llamadas

Lanza una llamada con un agente. Tu número (FROM) llama al destino (TO) y al contestar se conecta el agente.

POST /v1/calls
Authorization: Bearer nvk_live_...
Content-Type: application/json
Idempotency-Key: unique-request-id-123

{
  "agent_id": "uuid-del-agente",
  "from": "+18336528039",
  "to": "+12025551234",
  "dynamic_variables": {
    "nombre": "Carlos",
    "cita_fecha": "mañana a las 3 PM",
    "empresa": "Clínica Vista"
  },
  "external_ref": "appointment_98231"
}

# Respuesta 201
{
  "id": "CallSid",
  "agent_id": "uuid",
  "from": "+18336528039",
  "to": "+12025551234",
  "status": "queued",
  "rate_per_min": 0.18,
  "external_ref": "appointment_98231",
  "created_at": "..."
}

Obtener estado de la llamada

GET /v1/calls/{id}
# Devuelve cost, agent_cost, duration_seconds, status,
# recording_url, transcription, dynamic_variables, external_ref

Listar llamadas

GET /v1/calls?limit=50
GET /v1/calls?external_ref=appointment_98231   # filtrado por tu ID

Llamadas programadas

Pasá scheduled_at (ISO-8601, hasta 30 días en el futuro) a POST /v1/calls y la llamada queda encolada hasta ese momento.

POST /v1/calls
{
  "agent_id": "uuid",
  "from": "+18336528039",
  "to": "+5491155551234",
  "scheduled_at": "2026-06-15T14:30:00Z",
  "dynamic_variables": { "nombre": "Lucia" }
}

# Respuesta 202
{
  "object": "scheduled_call",
  "id": "uuid",
  "scheduled_at": "2026-06-15T14:30:00Z",
  "status": "pending"
}

Un cron worker en NeuraVoz dispara la llamada con tolerancia de ±1 minuto. Idempotency-Key también persiste en la cola para evitar duplicados.

Variables dinámicas

Cada llamada puede incluir hasta 50 variables de hasta 500 caracteres cada una. Se inyectan al agente al iniciar la sesión.

En el prompt y first_message del agente referenciás las variables con doble llave:

first_message: "Hola {{nombre}}, te llamo de {{empresa}}. Tu pedido {{orden}} sale {{fecha}}."

# Al hacer POST /v1/calls:
{
  "dynamic_variables": {
    "nombre": "Carlos",
    "empresa": "Vista",
    "orden": "ORD-8821",
    "fecha": "mañana"
  }
}

Nombres válidos: [a-zA-Z_][a-zA-Z0-9_]{0,63}. Caracteres de control se eliminan automáticamente.

Idempotency

POST /v1/calls acepta el header opcional Idempotency-Key. Si el cliente reintenta con la misma key dentro de 24h, la API devuelve la respuesta original sin crear una segunda llamada.

Recomendado siempre: en tu sistema usá un UUID por intención de llamada. Si tu cron reintenta, no duplicarás cargos.

Webhooks salientes

Recibí eventos en tu URL cuando ocurren cosas en NeuraVoz. Configurá endpoints en /dashboard/developers o vía API.

Crear endpoint

POST /v1/webhooks
{
  "url": "https://api.tuapp.com/webhooks/neuravoz",
  "events": ["call.completed", "call.failed"],
  "description": "Producción"
}

# Respuesta 201
{
  "id": "uuid",
  "url": "...",
  "events": ["call.completed", "call.failed"],
  "secret": "whsec_xxxxx"   ← solo se muestra al crear
}

Verificar firma

Cada request a tu endpoint incluye un header X-NeuraVoz-Signature: t=<timestamp>,v1=<hmac_hex>. Verificalo así:

# Node.js / TypeScript
import { createHmac } from "node:crypto";

function verify(secret: string, header: string, rawBody: string): boolean {
  const [tsPart, sigPart] = header.split(",");
  const ts = tsPart.split("=")[1];
  const sig = sigPart.split("=")[1];
  const mac = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return mac === sig && Math.abs(Date.now()/1000 - parseInt(ts, 10)) < 300; // 5-min tolerance
}

Payload

{
  "id": "evt_xxxx",                    # idempotente — usa para dedup
  "type": "call.completed",
  "created_at": "2026-05-20T12:34:56Z",
  "data": {
    "id": "CallSid",
    "from": "+18336528039",
    "to": "+12025551234",
    "duration_seconds": 47,
    "cost": 0.15,
    "agent_cost": 0.15,
    "status": "completed",
    "agent_id": "uuid",
    "external_ref": "appointment_98231",
    "dynamic_variables": { ... }
  }
}

Tu endpoint debe responder 2xx. Reintentos con backoff: 1m → 5m → 30m → 2h → 12h → dead. También usá el header X-NeuraVoz-Event-Id para idempotencia.

Catálogo de eventos

  • agent.created — un agente fue creado
  • agent.updated — actualizado
  • agent.deleted — eliminado
  • call.queued — llamada creada, en cola del provider
  • call.ringing — sonando
  • call.answered — contestada
  • call.completed — terminada (con duration, cost)
  • call.failed — falló (busy, no-answer, canceled, error)

Errores

Formato uniforme:

{
  "error": {
    "code": "insufficient_balance",
    "message": "Balance $0.20 below minimum $0.50 for agent calls"
  }
}

Códigos comunes: unauthorized (401), insufficient_scope (403), quota_exceeded (403), not_found (404), insufficient_balance (402), rate_limited (429), provider_error (502).

Rate limits

Por defecto: 60 requests por minuto por API key. Si lo excedés recibís 429 rate_limited. La ventana es de 1 minuto fijo (no sliding).

Para campañas masivas usá llamadas individuales con Idempotency-Key y reintentos exponenciales. SignalWire impone 1 outbound call/sec/número — si necesitás throughput, comprá varios números y rotá el from.

¿Necesitás un límite más alto? Escribí a soporte indicando el caso de uso esperado.