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 colouravatar_initials(string, optional) — 1–2 charsvoice_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 bysarvam,nextneural,elevenlabslanguage_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) —inboundoroutboundstatus(string, optional) —draft,active,pausedinclude_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) —inboundoroutboundname(string, required)provider(string, required) — TTS provider:sarvam,nextneural,elevenlabsvoice_agent_id(string, optional) — UUID of an existing agent; if omitted,speakercreates onespeaker(string, optional) — auto-create agent from this speaker namelanguage_code(string, optional) — e.g.hi-IN,en-USphone_number(string, optional) — inbound: the number to receive calls onscript_id(string, optional) — UUID of script to deliverbranches(array of strings, optional) — customer tag filters for outbound targetingstart_date/end_date(date, optional) — outbound campaign windowtime_window(object, optional) —{start, end, timezone}daily calling hoursconfig(object, optional) — arbitrary JSON; carries persona prompt, guardrails, lead-trigger field mappingsstatus(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 phonetags(string, repeatable, optional) — filter by tag values e.g.?tags=Region:West&tags=Segment:Lapsedlimit(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 keyemail(string, optional)tags(array of strings, optional)preferred_language(string, optional) —hi,en,hi-en,ta,te,kn,mr,bn,gu,ml,pametadata(object, optional) — any JSON; no PII in free-text valuesoverwrite(boolean, optional, default:false) — iftrue, silently upserts when a customer with the same phone already exists (merges metadata); iffalse, returns409 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 requestmode(string, optional, default:upsert) —upsert(merge by phone) orappend(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) —.csvor.jsonfile
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) —inboundoroutboundflow_uuid(string, optional)customer_uuid(string, optional)outcome(string, optional) —connected,voicemail,no-answer,failedsentiment(string, optional) —positive,neutral,negativedate_from/date_to(date, optional) — ISO 8601 datelimit(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_teamagent_brief(string, required) — ≤280 chars; handed to the receiving human agentcontext(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 active503 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 requestsevents(array of strings, required) — one or more event types (see below)headers(object, optional) — custom headers included in every delivery
Available event types:
| Event | Fired when |
|---|---|
call.started | A call connects (human voice detected) |
call.ended | A call terminates — includes full transcript and outcome |
call.transferred | AI initiates a warm transfer |
call.no_answer | Call goes unanswered after ring timeout |
call.voicemail | Voicemail detected — auto-disconnected |
campaign.launched | Outbound campaign starts |
campaign.stopped | Outbound campaign stops |
campaign.completed | All 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-Signatureheader (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 hyphenslanguage(string, required) — e.g.hi-IN,en-USdescription(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 secondstranscript(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
| Endpoint | Limit |
|---|---|
| All endpoints (default) | 100/minute per IP |
POST /flows/{uuid}/launch | 5/minute |
POST /flows/{uuid}/stop | 5/minute |
POST /customers/bulk | 10/minute |
POST /customers/import | 5/minute |
POST /customers | 60/minute |
PATCH /customers/{uuid} | 60/minute |
POST /models/speakers | 5/minute |
POST /models/speakers/{name}/deploy | 5/minute |
POST /models/speakers/{name}/dataset/sample | 30/minute |
POST /calls/{uuid}/transfer | 10/minute |
POST /calls/{uuid}/end | 10/minute |
GET /calls/agent-availability | 30/minute |
POST /calls/{uuid}/metadata | 30/minute |
POST /webhooks | 20/minute |
POST /agents | 20/minute |
POST /flows | 20/minute |