Skip to main content

Voice AI

The Voice AI agent provides a complete outbound and inbound voice calling platform. It handles campaign dialing, real-time multilingual conversation via AI personas, live transcript streaming, call recording, and post-call structured extraction. External workflow services can subscribe to call events and push actions back via the action endpoints.

Base URL

/api/voice

Authentication

All endpoints require a valid API key or Firebase token unless noted otherwise.

Authorization: Bearer <token-or-api-key>

API keys use the prefix nn_k_. WebSocket endpoints accept the token as a query parameter ?token=<value>.

Rate Limits

The platform applies a global default of 100 requests per minute per IP. Endpoints that trigger telephony actions or bulk writes have tighter per-endpoint limits noted inline. Exceeding a limit returns 429 Too Many Requests.


Voice Agents

A Voice Agent is an AI persona — name, role, and voice configuration — that drives calls. Agents are reusable across flows.

List Voice Agents

GET /agents

Returns all active voice agents for the organisation.

Request example:

curl "https://nextneural-api.superteams.ai/api/voice/agents" \
-H "Authorization: Bearer YOUR_TOKEN"

Response:

[
{
"uuid": "agt_a1b2c3d4",
"name": "Aria",
"role": "Outbound Sales",
"description": "Multilingual outbound agent for retail sales",
"avatar_color": "#6366f1",
"avatar_initials": "AR",
"voice_config": {
"provider": "sarvam",
"model": "bulbul:v3",
"speaker": "anushka",
"language_code": "hi-IN",
"sample_url": "https://cdn.nextneural.ai/samples/anushka.mp3"
},
"is_active": true,
"created_at": "2026-05-01T10:00:00Z"
}
]

Get Voice Agent

GET /agents/{agent_uuid}

Path parameters:

  • agent_uuid (string, required) — UUID of the agent

Create Voice Agent

POST /agents

Rate limit: 20/minute

Request body:

{
"name": "Aria",
"role": "Outbound Sales",
"description": "Multilingual outbound agent for retail sales",
"avatar_color": "#6366f1",
"avatar_initials": "AR",
"voice_config": {
"provider": "sarvam",
"model": "bulbul:v3",
"speaker": "anushka",
"language_code": "hi-IN"
}
}

Fields:

  • name (string, required)
  • role (string, required)
  • description (string, optional)
  • avatar_color (string, optional) — hex colour
  • avatar_initials (string, optional) — 1–2 chars
  • voice_config (object, optional) — provider, model, speaker, language_code

Update Voice Agent

PATCH /agents/{agent_uuid}

Partial update. Same fields as create, all optional.


Delete Voice Agent

DELETE /agents/{agent_uuid}

Soft delete — sets is_active = false. Does not affect running calls.


List Voice Samples

GET /samples

Returns available TTS voice samples from all connected providers.

Query parameters:

  • provider (string, optional) — filter by sarvam, nextneural, elevenlabs
  • language_code (string, optional) — e.g. hi-IN, en-IN

Response:

[
{
"uuid": "vs_x1y2z3",
"provider": "sarvam",
"model": "bulbul:v3",
"speaker": "anushka",
"language_code": "hi-IN",
"language_name": "Hindi",
"url": "https://cdn.nextneural.ai/samples/anushka.mp3"
}
]

Flows

A Flow is either an inbound line (tied to a phone number) or an outbound campaign (targeting a customer list). Flows reference a Voice Agent and carry the system prompt / script configuration.

List Flows

GET /flows

Query parameters:

  • kind (string, optional) — inbound or outbound
  • status (string, optional) — draft, active, paused
  • include_metrics (boolean, optional, default: false) — attach live metrics to each flow

Response includes per-flow metrics when requested:

{
"flows": [
{
"uuid": "flw_f1g2h3",
"kind": "outbound",
"name": "Spring Reactivation Campaign",
"status": "active",
"voice_agent_id": "agt_a1b2c3d4",
"phone_number": "+15550010000",
"script_id": "scr_s1t2u3",
"branches": ["Region:West", "Segment:Lapsed"],
"start_date": "2026-05-01",
"end_date": "2026-05-31",
"time_window": {"start": "09:00", "end": "18:00", "timezone": "America/New_York"},
"config": {
"persona_prompt": "You are Aria, a friendly advisor at Luminary Home...",
"recording_disclosure": "This call is being recorded for quality purposes.",
"guardrails": ["no_price_fabrication", "no_stock_fabrication"]
},
"metrics": {
"calls_today": 142,
"total_calls": 891,
"connect_rate": 0.61,
"answer_rate": 0.74,
"missed_rate": 0.26,
"avg_handle_time": "01:32",
"peak_hour": 11
},
"created_at": "2026-05-01T00:00:00Z"
}
]
}

Get Flow

GET /flows/{flow_uuid}

Query parameters:

  • include_metrics (boolean, optional, default: false)

Create Flow

POST /flows

Rate limit: 20/minute

Request body:

{
"kind": "outbound",
"name": "Spring Reactivation Campaign",
"provider": "sarvam",
"voice_agent_id": "agt_a1b2c3d4",
"phone_number": "+15550010000",
"script_id": "scr_s1t2u3",
"branches": ["Region:West", "Segment:Lapsed"],
"start_date": "2026-05-01",
"end_date": "2026-05-31",
"time_window": {"start": "09:00", "end": "18:00", "timezone": "America/New_York"},
"config": {
"persona_prompt": "You are Aria, a friendly advisor at Luminary Home...",
"recording_disclosure": "This call is being recorded for quality purposes.",
"guardrails": ["no_price_fabrication", "no_stock_fabrication"]
},
"status": "draft"
}

Fields:

  • kind (string, required) — inbound or outbound
  • name (string, required)
  • provider (string, required) — TTS provider: sarvam, nextneural, elevenlabs
  • voice_agent_id (string, optional) — UUID of an existing agent; if omitted, speaker creates one
  • speaker (string, optional) — auto-create agent from this speaker name
  • language_code (string, optional) — e.g. hi-IN, en-US
  • phone_number (string, optional) — inbound: the number to receive calls on
  • script_id (string, optional) — UUID of script to deliver
  • branches (array of strings, optional) — customer tag filters for outbound targeting
  • start_date / end_date (date, optional) — outbound campaign window
  • time_window (object, optional) — {start, end, timezone} daily calling hours
  • config (object, optional) — arbitrary JSON; carries persona prompt, guardrails, lead-trigger field mappings
  • status (string, optional, default: draft)

Update Flow

PATCH /flows/{flow_uuid}

Partial update. All fields optional.


Toggle Flow Status

POST /flows/{flow_uuid}/toggle

Switches between active and paused. No body required.


Launch Outbound Campaign

POST /flows/{flow_uuid}/launch

Rate limit: 5/minute

Starts the outbound dialer for this flow. Filters customers by branches tags, respects time_window and start_date/end_date. Deduplicates against customers dialled recently.

Response:

{
"status": "launched",
"flow_uuid": "flw_f1g2h3",
"customers_queued": 284
}

Stop Outbound Campaign

POST /flows/{flow_uuid}/stop

Stops a running campaign. In-progress calls are not dropped.

Response:

{
"status": "stopped",
"flow_uuid": "flw_f1g2h3"
}

Delete Flow

DELETE /flows/{flow_uuid}

Stops any running campaign for this flow, then deletes the flow record.


Customers

Customers are the contact list. Each customer carries tags for flow targeting, a preferred language, and an arbitrary metadata object for lead-trigger data (cart contents, product interest, budget signals, etc.).

List Customers

GET /customers

Query parameters:

  • search (string, optional) — searches name and phone
  • tags (string, repeatable, optional) — filter by tag values e.g. ?tags=Region:West&tags=Segment:Lapsed
  • limit (integer, optional, default: 50, max: 100)
  • offset (integer, optional, default: 0)
  • min_inbound_calls / max_inbound_calls (integer, optional)
  • min_outbound_calls / max_outbound_calls (integer, optional)

Response:

{
"customers": [
{
"uuid": "cst_c1d2e3",
"name": "Jordan Ellis",
"phone": "+14155550192",
"email": "[email protected]",
"tags": ["Region:West", "Segment:Lapsed"],
"preferred_language": "en",
"metadata": {
"product_interest": "Standing Desk Pro",
"cart_value": 850,
"zip_code": "94107",
"lead_type": "cart_abandon",
"lead_id": "LH-CA-2026050512-78231"
},
"total_calls": 2,
"inbound_calls": 0,
"outbound_calls": 2,
"last_contact": "2026-05-12T14:34:00Z",
"created_at": "2026-05-01T00:00:00Z"
}
],
"total": 1482
}

Get Customer

GET /customers/{customer_uuid}

Query parameters:

  • include_calls (boolean, optional, default: false) — attach recent call history

Create Customer

POST /customers

Rate limit: 60/minute

Request body:

{
"name": "Jordan Ellis",
"phone": "+14155550192",
"email": "[email protected]",
"tags": ["Region:West", "Segment:Lapsed"],
"preferred_language": "en",
"metadata": {
"product_interest": "Standing Desk Pro",
"cart_value": 850,
"zip_code": "94107",
"lead_type": "cart_abandon",
"lead_id": "LH-CA-2026050512-78231"
}
}

Fields:

  • name (string, required)
  • phone (string, required) — E.164 format; used as upsert key
  • email (string, optional)
  • tags (array of strings, optional)
  • preferred_language (string, optional) — hi, en, hi-en, ta, te, kn, mr, bn, gu, ml, pa
  • metadata (object, optional) — any JSON; no PII in free-text values
  • overwrite (boolean, optional, default: false) — if true, silently upserts when a customer with the same phone already exists (merges metadata); if false, returns 409 Conflict

Update Customer

PATCH /customers/{customer_uuid}

Rate limit: 60/minute

Partial update. All fields optional. metadata is merged (not replaced) — pass only the keys you want to change.

Request body:

{
"tags": ["Region:West", "Segment:Lapsed", "Priority:High"],
"metadata": {
"cart_value": 920
}
}

Bulk Upsert Customers

POST /customers/bulk

Rate limit: 10/minute

Upserts a batch of customers by phone number. Existing records are updated; new records are created. Designed for daily lead-list pushes from external CRMs.

Request body:

{
"customers": [
{
"name": "Jordan Ellis",
"phone": "+14155550192",
"tags": ["Region:West", "Segment:Lapsed"],
"preferred_language": "en",
"metadata": {
"product_interest": "Standing Desk Pro",
"cart_value": 850,
"zip_code": "94107",
"lead_type": "cart_abandon",
"lead_id": "LH-CA-2026050512-78231"
}
}
],
"mode": "upsert"
}

Fields:

  • customers (array, required) — up to 1000 records per request
  • mode (string, optional, default: upsert) — upsert (merge by phone) or append (always create)

Response:

{
"created": 218,
"updated": 66,
"errors": [
{ "row": 4, "phone": "+10000000000", "reason": "invalid phone format" }
]
}

Notes:

  • Errors are isolated per row — one bad record does not fail the batch
  • Maximum 1000 customers per request; paginate for larger lists

Import Customers from File

POST /customers/import

Rate limit: 5/minute

Imports customers from a CSV or JSON file. Upserts by phone number.

Request: multipart/form-data

  • file (required) — .csv or .json file

CSV format:

name,phone,email,tags,preferred_language,metadata
Jordan Ellis,+14155550192,[email protected],"Region:West,Segment:Lapsed",en,"{""cart_value"":850}"

JSON format:

[
{
"name": "Jordan Ellis",
"phone": "+14155550192",
"tags": ["Region:West"],
"metadata": { "cart_value": 850 }
}
]

Response:

{
"imported": 284,
"updated": 41,
"skipped": 2,
"errors": []
}

Customer Metadata Endpoints

GET /customers/meta/tags

Returns all unique tag values across the organisation's customers.

GET /customers/meta/branches

Returns unique branch names (tags prefixed with Branch:).

GET /customers/meta/count

Query parameters:

  • tags (string, repeatable) — count customers matching any of these tags

Response: { "count": 312 }


Calls

List Calls

GET /calls

Query parameters:

  • call_type (string, optional) — inbound or outbound
  • flow_uuid (string, optional)
  • customer_uuid (string, optional)
  • outcome (string, optional) — connected, voicemail, no-answer, failed
  • sentiment (string, optional) — positive, neutral, negative
  • date_from / date_to (date, optional) — ISO 8601 date
  • limit (integer, optional, default: 50, max: 100)
  • offset (integer, optional, default: 0)

Response:

{
"calls": [
{
"uuid": "cal_c1d2e3f4",
"call_type": "outbound",
"flow_uuid": "flw_f1g2h3",
"customer_uuid": "cst_c1d2e3",
"voice_agent_uuid": "agt_a1b2c3d4",
"from_number": "+15550010000",
"to_number": "+14155550192",
"duration": 113,
"duration_formatted": "01:53",
"outcome": "connected",
"sentiment": "positive",
"summary": "Customer interested in Standing Desk Pro, delivery timeline query, transferred to specialist.",
"recording_url": "https://nextneural-api.superteams.ai/api/voice/calls/cal_c1d2e3f4/recording",
"key_details": {
"extraction": { "...": "structured extraction payload" },
"qms_scores": { "...": "quality scores" }
},
"started_at": "2026-05-12T14:32:01Z",
"ended_at": "2026-05-12T14:34:00Z"
}
],
"total": 891
}

Get Call

GET /calls/{call_uuid}

Returns full call record including the complete transcript.

Response includes:

{
"uuid": "cal_c1d2e3f4",
"transcript": [
{ "speaker": "agent", "text": "Hi, good morning! This is Aria calling from Luminary Home.", "timestamp": 0.0 },
{ "speaker": "customer", "text": "Oh hey, yes I was looking at that desk.", "timestamp": 6.2 }
],
"key_details": { "...": "structured extraction and quality scoring payload" }
}

Get Call Statistics

GET /calls/statistics

Aggregated metrics for a flow or date range.

Query parameters:

  • flow_uuid (string, optional)
  • date_from / date_to (date, optional)

Response:

{
"total_calls": 891,
"connected": 543,
"voicemail": 124,
"no_answer": 189,
"failed": 35,
"connect_rate": 0.61,
"sentiment_breakdown": {
"positive": 312,
"neutral": 189,
"negative": 42
},
"avg_duration_seconds": 97
}

List Live Calls

GET /calls/live

Returns currently active inbound calls (in-memory registry). Useful for supervisor dashboards.


Get Call Recording

GET /calls/{call_uuid}/recording

Query parameters:

  • token (string, required) — Firebase token or API key

Proxies the telephony provider MP3 recording with auth. Returns audio stream. Returns 404 if no recording exists or the customer opted out.


Write Call Metadata (Workflow Action)

POST /calls/{call_uuid}/metadata

Rate limit: 30/minute

Allows an external workflow service to write structured extraction results back to a call record after processing. Merges into the key_details JSON field.

Request body:

{
"key_details": {
"extraction": {
"schema_version": "1.0",
"outcome": {
"call_outcome": "warm_transfer",
"intent_score": 88,
"verbatim_quote": "I want to order it today, just need the delivery date confirmed"
},
"requirement": {
"product_category": "furniture",
"budget_signal": 850,
"timeline": "today",
"zip_code": "94107",
"city": "San Francisco"
},
"objection": {
"category": "delivery_time",
"detail": "Needs delivery within 3 days, standard is 5–7",
"competitor_mentioned": "none"
},
"disposition": {
"lead_stage": "Interested in Buying",
"reason": "Delivery Query",
"sub_reason": "Query Escalated",
"sub_sub_reason": "Specialist Transfer"
},
"next_action": {
"type": "warm_transfer",
"transfer_target": "sales_specialist",
"agent_brief": "Standing Desk Pro, $850, ready to buy today, needs delivery confirmed within 3 days to zip 94107."
}
},
"qms_scores": {
"opening": 3,
"energy": 2,
"probing": 3
}
}
}

Response:

{
"uuid": "cal_c1d2e3f4",
"key_details": { "...": "merged result" },
"updated_at": "2026-05-12T14:35:00Z"
}

Initiate Transfer (Workflow Action)

POST /calls/{call_uuid}/transfer

Rate limit: 10/minute

Instructs the platform to bridge an active call to a human agent queue. Only valid while the call is live.

Request body:

{
"target": "sales_specialist",
"agent_brief": "Standing Desk Pro, $850, ready to buy today, needs delivery confirmed within 3 days to zip 94107.",
"context": {
"intent_score": 88,
"product": "Standing Desk Pro",
"cart_value": 850
}
}

Fields:

  • target (string, required) — target queue name configured in your organisation; e.g. sales_specialist, support_team, retention_team
  • agent_brief (string, required) — ≤280 chars; handed to the receiving human agent
  • context (object, optional) — arbitrary JSON attached to the transfer event

Response:

{
"status": "transfer_initiated",
"call_uuid": "cal_c1d2e3f4",
"target": "sales_specialist"
}

Errors:

  • 409 Conflict — call is not currently active
  • 503 Service Unavailable — no agents available in target queue

End Call (Workflow Action)

POST /calls/{call_uuid}/end

Rate limit: 10/minute

Terminates an active call programmatically. Used by workflow services for DNC requests, hostile callers, or wrong-number dispositions.

Request body:

{
"reason": "dnc_request"
}

Fields:

  • reason (string, optional) — dnc_request, wrong_number, hostile_caller, workflow_close

Check Agent Availability (Workflow Action)

GET /calls/agent-availability

Rate limit: 30/minute

Returns whether human agents are currently available in a given queue. Used by workflow services before deciding between a warm transfer and an async handoff.

Query parameters:

  • target (string, required) — target queue name; e.g. sales_specialist, support_team

Response:

{
"target": "sales_specialist",
"available": true,
"queue_depth": 2,
"checked_at": "2026-05-12T14:32:00Z"
}

Webhooks

Webhooks allow external systems to receive real-time events from the platform. A workflow service subscribes here to drive post-call extraction, CRM writes, and downstream messaging triggers.

List Webhooks

GET /webhooks

Query parameters:

  • is_active (boolean, optional)

Response:

{
"webhooks": [
{
"uuid": "wbh_w1x2y3",
"name": "CRM Sync Workflow",
"url": "https://workflow.example.internal/nn-events",
"events": ["call.ended", "call.started", "call.transferred"],
"is_active": true,
"last_triggered_at": "2026-05-12T14:34:00Z"
}
]
}

Get Webhook

GET /webhooks/{webhook_uuid}


Create Webhook

POST /webhooks

Rate limit: 20/minute

Request body:

{
"name": "CRM Sync Workflow",
"url": "https://workflow.example.internal/nn-events",
"events": ["call.ended", "call.started", "call.transferred"],
"headers": {
"X-Workflow-Secret": "your-shared-secret"
}
}

Fields:

  • name (string, required)
  • url (string, required) — HTTPS endpoint that receives POST requests
  • events (array of strings, required) — one or more event types (see below)
  • headers (object, optional) — custom headers included in every delivery

Available event types:

EventFired when
call.startedA call connects (human voice detected)
call.endedA call terminates — includes full transcript and outcome
call.transferredAI initiates a warm transfer
call.no_answerCall goes unanswered after ring timeout
call.voicemailVoicemail detected — auto-disconnected
campaign.launchedOutbound campaign starts
campaign.stoppedOutbound campaign stops
campaign.completedAll customers in campaign have been dialled

Get Webhook Logs

GET /webhooks/{webhook_uuid}/logs

Query parameters:

  • limit (integer, optional, default: 50, max: 100)
  • offset (integer, optional, default: 0)

Response:

{
"logs": [
{
"uuid": "wbl_l1m2n3",
"event_type": "call.ended",
"payload": { "...": "event payload" },
"response_status": 200,
"success": true,
"triggered_at": "2026-05-12T14:34:05Z"
}
]
}

Webhook Event Payload

Every webhook delivery is a POST to your URL with the following envelope:

{
"event": "call.ended",
"fired_at": "2026-05-12T14:34:05Z",
"organisation_uuid": "org_o1p2q3",
"data": {
"call_uuid": "cal_c1d2e3f4",
"call_type": "outbound",
"flow_uuid": "flw_f1g2h3",
"customer_uuid": "cst_c1d2e3",
"customer_phone": "+14155550192",
"customer_metadata": {
"lead_id": "LH-CA-2026050512-78231",
"lead_type": "cart_abandon",
"product_interest": "Standing Desk Pro",
"cart_value": 850,
"zip_code": "94107"
},
"outcome": "connected",
"sentiment": "positive",
"duration_seconds": 113,
"language_detected": "en",
"transcript": [
{ "speaker": "agent", "text": "Hi, good morning! This is Aria calling from Luminary Home.", "timestamp": 0.0 },
{ "speaker": "customer", "text": "Oh hey, yes I was looking at that desk.", "timestamp": 6.2 }
],
"recording_url": "https://nextneural-api.superteams.ai/api/voice/calls/cal_c1d2e3f4/recording",
"started_at": "2026-05-12T14:32:01Z",
"ended_at": "2026-05-12T14:34:00Z"
}
}

Notes:

  • Deliveries are retried up to 3 times with exponential backoff on non-2xx responses
  • Verify deliveries using the X-NN-Signature header (HMAC-SHA256 of the raw body signed with your webhook secret)
  • Respond within 10 seconds; slow endpoints will time out and be retried

Voice Models (Voice Cloning)

NextNeural supports zero-shot voice cloning and LoRA fine-tuning to create custom speaker voices that can be deployed to the shared voice catalog.

List Speakers

GET /models/speakers

Returns all cloned speakers for the organisation with their deploy status.

Response:

[
{
"name": "aria-custom",
"language": "en-US",
"description": "Custom US English sales voice",
"deployed": true,
"voice_sample_uuid": "vs_x1y2z3",
"created_at": "2026-05-01T00:00:00Z"
}
]

Create Speaker

POST /models/speakers

Rate limit: 5/minute

Creates a new speaker via zero-shot voice cloning from a reference audio clip.

Request: multipart/form-data

  • name (string, required) — unique speaker identifier, lowercase with hyphens
  • language (string, required) — e.g. hi-IN, en-US
  • description (string, optional)
  • reference_audio (file, required) — WAV or MP3, 10–60 seconds, clean speech

Response:

{
"name": "aria-custom",
"language": "en-US",
"status": "created",
"message": "Speaker created. Upload dataset samples and deploy to use in flows."
}

Get Speaker

GET /models/speakers/{name}


Delete Speaker

DELETE /models/speakers/{name}

Deletes the speaker and its dataset. Undeploys from catalog first if deployed.


Get Reference Audio

GET /models/speakers/{name}/reference

Streams the reference audio clip used to create the speaker. Returns audio stream.


Get Dataset

GET /models/speakers/{name}/dataset

Returns dataset statistics for LoRA fine-tuning.

Response:

{
"name": "aria-custom",
"sample_count": 47,
"total_duration_seconds": 312,
"status": "ready"
}

Upload Dataset Sample

POST /models/speakers/{name}/dataset/sample

Rate limit: 30/minute

Uploads a single labelled audio sample for LoRA fine-tuning.

Request: multipart/form-data

  • audio (file, required) — WAV or MP3, 5–30 seconds
  • transcript (string, required) — exact text spoken in the audio

Response:

{
"sample_id": "smp_s1t2u3",
"duration_seconds": 8.4,
"transcript": "Hi, good morning! This is Aria calling from Luminary Home."
}

Clear Dataset

DELETE /models/speakers/{name}/dataset

Removes all uploaded dataset samples. Does not delete the speaker.


Deploy Speaker

POST /models/speakers/{name}/deploy

Rate limit: 5/minute

Publishes the speaker to the shared voice catalog, making it available as a voice sample when creating agents and flows.

Response:

{
"name": "aria-custom",
"deployed": true,
"voice_sample_uuid": "vs_x1y2z3"
}

Undeploy Speaker

DELETE /models/speakers/{name}/deploy

Removes the speaker from the voice catalog. Does not delete the speaker or its dataset. Flows using this voice continue to work until they are updated.


Integrations

List Telephony Providers

GET /integrations/telephony

Returns supported telephony providers and their connection status for the organisation.


List App Integrations

GET /integrations/apps

Query parameters:

  • category (string, optional) — e.g. crm, calendar, messaging

Exotel

GET /integrations/telephony/exotel

Returns current Exotel configuration. Credentials are masked — only the last 4 characters of the API token are shown.

Response:

{
"provider": "exotel",
"account_sid": "your_account_sid",
"api_key": "••••••••key",
"api_token": "••••••••9abc",
"subdomain": "api.exotel.com",
"phone_number": "+15550010000",
"is_connected": true,
"connected_at": "2026-05-01T10:00:00Z"
}

PUT /integrations/telephony/exotel

Request body:

{
"api_key": "your_exotel_api_key",
"api_token": "your_exotel_api_token",
"account_sid": "your_account_sid",
"subdomain": "api.exotel.com",
"phone_number": "+15550010000"
}

DELETE /integrations/telephony/exotel

Disconnects Exotel. Running campaigns must be stopped first.


Sarvam

GET /integrations/voice/sarvam

PUT /integrations/voice/sarvam

Request body:

{
"api_key": "your_sarvam_api_key"
}

DELETE /integrations/voice/sarvam


WebSockets

Telephony Voicebot

WebSocket /ws/exotel

Entry point for the telephony provider voicebot applet. The provider opens this socket when a call connects. The platform resolves the flow from the account SID and phone number, then runs the full pipeline (VAD → STT → LLM → TTS). Not intended for direct client use.


Live Transcript Stream

WebSocket /ws/live/{call_uuid}?token=<token>

Streams real-time transcript events for a specific call. Used by supervisor dashboards and workflow services that need to monitor live calls.

Authentication: ?token=<firebase-token-or-api-key>

Event types received:

{ "type": "start", "call_uuid": "cal_c1d2e3f4", "started_at": "2026-05-12T14:32:01Z" }
{ "type": "transcript", "speaker": "agent", "text": "Hi, good morning...", "timestamp": 0.0 }
{ "type": "transcript", "speaker": "customer", "text": "Yes I was looking at that desk.", "timestamp": 6.2 }
{ "type": "status", "status": "transfer_initiated", "target": "sales_specialist" }
{ "type": "end", "outcome": "warm_transfer", "duration_seconds": 113 }

Connecting after the call has ended replays up to 200 buffered events from a 10-minute window.


Browser Test

WebSocket /ws/browser-test/{flow_uuid}?token=<token>

Enables browser-based testing of a flow without placing a real telephony call. Accepts raw PCM audio from the browser microphone and returns TTS audio and transcripts. Used during flow setup and QA.


Inbound Webhooks

Telephony Status Callback

POST /webhooks/exotel/status

Receives call status events from the telephony provider (answered, completed, failed). Not authenticated — the endpoint validates the account SID in the payload. Do not call this directly.


Error Responses

All endpoints return standard error shapes:

400 Bad Request:

{ "detail": "phone must be in E.164 format" }

401 Unauthorized:

{ "detail": "Invalid or expired token" }

404 Not Found:

{ "detail": "Customer not found" }

409 Conflict:

{ "detail": "Call is not currently active" }

429 Too Many Requests:

{ "detail": "Rate limit exceeded: 10/minute" }

503 Service Unavailable:

{ "detail": "No agents available in target queue" }

Workflow Integration Pattern

External workflow services integrate with the platform via webhooks and action endpoints, keeping all client-specific business logic outside the platform.

Standard post-call flow:

1. POST /customers/bulk            ← push lead list from your CRM daily
2. POST /flows/{uuid}/launch ← start campaign
3. Subscribe: webhook call.ended ← receive transcript on every call end
4. [Workflow] run extraction ← LLM pass over transcript
5. POST /calls/{uuid}/metadata ← write structured payload back
6. [Workflow] write to your CRM ← push extracted payload
7. [Workflow] trigger messaging ← fire WhatsApp, email, or callback queue

Warm transfer flow:

3b. Subscribe: webhook call.started    ← detect live call
4b. GET /calls/agent-availability ← check queue before deciding
5b. POST /calls/{uuid}/transfer ← initiate bridge if agent available
↳ falls back to async handoff if queue unavailable

The platform is workflow-agnostic. The config field on Flow and the metadata field on Customer carry all client-specific data. The workflow service owns the business logic; the platform owns the telephony and transcript infrastructure.


Rate Limit Reference

EndpointLimit
All endpoints (default)100/minute per IP
POST /flows/{uuid}/launch5/minute
POST /flows/{uuid}/stop5/minute
POST /customers/bulk10/minute
POST /customers/import5/minute
POST /customers60/minute
PATCH /customers/{uuid}60/minute
POST /models/speakers5/minute
POST /models/speakers/{name}/deploy5/minute
POST /models/speakers/{name}/dataset/sample30/minute
POST /calls/{uuid}/transfer10/minute
POST /calls/{uuid}/end10/minute
GET /calls/agent-availability30/minute
POST /calls/{uuid}/metadata30/minute
POST /webhooks20/minute
POST /agents20/minute
POST /flows20/minute