For Mintlify-style interactive docs (grouped endpoints, Try it, request/response panels), open the API reference tab — start at Introduction, then use the Voice Agent API sidebar (generated from
kupe-voice-agent.openapi.yaml). Try GET /health first (no API key), then GET /api/v1/me with your x-api-key or Bearer token.Base URL and authentication
All protected routes use the production API origin:https://api.kupe.in
For server integrations, send the Kupe API key (no Bearer prefix):
/api/v1/** routes, backend resolves user context from the key/JWT automatically (for example file upload/listing endpoints).
For dashboard / browser flows you can instead send a Supabase session token:
GET /api/v1/me works with either x-api-key or Bearer and returns user_id, email, optional phone, profile_image_url (when set on the auth user or OAuth provider metadata), auth (api_key or bearer), full_name, and a sanitized user_metadata object.
Sandbox values used for this document
These credentials were used to manually verify connectivity againsthttps://api.kupe.in on 2026-04-23 (see Live verification below).
| Variable | Value |
|---|---|
| API key | N8WJOk0TSvUGswbCk8EcaZQh6Symt0mQ |
| User ID | fbbf7286-4679-4bc1-983a-6d09a048be12 |
Field mapping (client checklist → Kupe)
Your integration checklist maps to Kupe as follows.| Your concept | Kupe implementation |
|---|---|
| name | agent.name on POST /api/v1/agents/simple (then optional PUT /api/v1/agents/{agent_id} to rename) |
| prompt | agent_model_config.system_prompt |
| call_type | Inbound: map a phone line to an assistant via POST /api/v1/phone/mapping (see Incoming calls) or the dashboard. Outbound: POST /api/call/create_call with agent_id |
| transcriber | transcriber_config (transcriber_model_name, language) — legacy provider_id still accepted; pipeline STT model id comes from the catalog row |
| model | agent_model_config (model_name, …) — legacy model_provider_id still accepted |
| voice | tts_config (tts_model_name, optional language, voice_name, voice_parameters) — legacy tts_provider_id still accepted |
| behavior / timeouts / transfer | agent_specific_config on POST /api/v1/agents or PUT /api/v1/agents/{agent_id} (see Agent-specific configuration) |
| inbound noise reduction | agent.apply_noise_reduction plus agent_specific_config.noise_cancellation_strength (0–100) |
| outbound background noise | GET /api/v1/noise-assets → agent_specific_config.background_noise_id (+ optional background_noise_volume) |
| extracted_variables | Configure Post analysis (structured output / HTTP tool) on the agent in the dashboard; see Webhooks |
| context_params | Passed at call time via your own telephony URL parameters or automation; the stock create_call query API does not accept a JSON body—extend or wrap if you need a single JSON contract |
Incoming calls (PSTN) — map a line to an assistant
When someone dials your purchased PSTN number, Kupe needs a mapping from that line to the agent (or workflow) that should run. Number format: store and send destinations in international E.164 where possible — for example +91 plus ten digits for a typical India mobile, or +1 plus ten digits for a US/Canada NANP number (URL-encode+ as %2B in query strings).
- Register the line under your account (if it is not already). Use the
/api/v1/phoneroutes in Platform REST APIs and the Incoming calls group in the OpenAPI sidebar (Try it onPOST /api/v1/phone/mapping). - Create the mapping with
POST https://api.kupe.in/api/v1/phone/mapping:
- Send exactly one of
agent_idorworkflow_id. - Authenticate with
x-api-keyor Bearer JWT. You do not needwebhook_url,config_source, oruser_idin the body; the server fills those from deployment defaults and the authenticated user when they are omitted.
GET /api/v1/phone/mapping/agent/{agent_id}, GET /api/v1/phone/mapping/{phone_number_id}.
1. Agent management
List agents (summary)
GET /api/v1/agents/summary
Query: page, page_size, optional name_search.
Get full agent
GET /api/v1/agents/{agent_id}
Returns CompleteAgentResponse: agent plus configurations (model_config, tts_config, transcriber_config, vad_config, inferencing_config, tools, agent_specific_config).
Create agent
Two create paths:| Path | When to use |
|---|---|
POST /api/v1/agents/simple | Name (+ optional description) only; platform defaults for STT, TTS, LLM, VAD, and agent_specific_config. |
POST /api/v1/agents | Full create in one shot: agent, optional agent_model_config, tts_config, transcriber_config, agent_specific_config, and tools. |
POST /api/v1/agents/simple, configure models, voice, and behavior with PUT /api/v1/agents/{agent_id} (partial body). VAD and inferencing stay on platform defaults unless you change them in advanced flows.
Set the welcome message under agent.first_response_message, or agent.welcome_message (alias). GET /api/v1/agents/{agent_id} returns it on agent.first_response_message.
Catalog strings: use model_name, tts_model_name, transcriber_model_name (legacy *_provider_id / STT provider_id still work). See Model providers and Supported models & providers.
Agent-specific configuration
Per-agent runtime behavior lives inagent_specific_config (stored in agent_specific_configs.configuration). You can set it on:
POST /api/v1/agents— includeagent_specific_configin the create body (omitted keys use platform defaults).PUT /api/v1/agents/{agent_id}— send only the keys to change; the API merges them onto the stored config.
GET /api/v1/agents/{agent_id} → configurations.agent_specific_config.
| Key | Type | Purpose |
|---|---|---|
session_timeout | integer (seconds) | Max session length before auto disconnect (default 180). |
end_of_speech_timeout | number | Seconds after the user stops speaking before the pipeline processes (default 1.5). |
llm_conversation_stop_enabled | boolean | When true, the LLM can end the call when the user wants to stop (terminate_call). |
auto_terminate_call | boolean | When true, the agent may hang up via terminate_call (dashboard alias autoTerminate). |
caller_gender_detection_enabled | boolean | Detect caller gender for prompt context (default true). |
max_output_tokens | integer | Cap LLM output tokens per turn. |
timezone | string | IANA timezone for date/time in prompts (e.g. Asia/Kolkata). |
inbound_transfer_routes | array | Conditional PSTN transfers (route_id, condition_when_to_transfer, phone_number, …). |
inbound_transfer_call_number | string | Legacy single transfer target (digits only); use routes when you need multiple targets. |
inbound_transfer_trigger_condition | string | Legacy NL condition for the single-number transfer. |
conversation_stop_words | string[] | Legacy — ignored at runtime. Use llm_conversation_stop_enabled instead. |
time_to_cut | integer (seconds) | Warn the caller this many seconds before session_timeout ends. |
message_of_cut | string | Spoken warning at time_to_cut when end_call_message_mode is fixed. |
end_call_message_mode | string | fixed (use message_of_cut) or context_aware (LLM-generated short warning). |
noise_cancellation_strength | integer (0–100) | Spectral subtraction strength when agent.apply_noise_reduction is true (50 = default). |
background_noise_id | string | Outbound ambient track id from List noise assets. Empty = none. |
background_noise_volume | integer (0–100) | Gain for outbound background ambience (100 = full, 0 = mute). Default 100. |
agent.apply_noise_reduction on the same create/update body (not inside agent_specific_config) to enable inbound noise reduction before VAD/STT.
Example — create with behavior
AgentSpecificConfigInput in the API reference.
List background noise assets
GET /api/v1/noise-assets
Returns the catalog of WAV assets you can assign to an agent for outbound room tone (mixed under agent speech on the live WebSocket path). Authenticate with x-api-key or Authorization: Bearer.
Each row includes:
| Field | Description |
|---|---|
id | Pass to agent_specific_config.background_noise_id (often the filename, e.g. office_noise.wav). |
name | Display label (filename without extension). |
[] means no noise files are deployed on the server yet.
No background voice
To send only agent speech with no ambient track:- Create: omit
background_noise_idfromagent_specific_config. - Update (clear):
"background_noise_id": nullor"background_noise_id": "". - Verify:
GET /api/v1/agents/{agent_id}→configurations.agent_specific_configshould not contain a non-emptybackground_noise_id. - Dashboard: choose None under Background noise asset (same as clearing via API).
- Mute only: keep an
idbut set"background_noise_volume": 0.
Create simple agent (defaults only)
POST /api/v1/agents/simple
Body: { "name": "...", "description": "..." } — fastest path when defaults are acceptable end-to-end. Set agent_specific_config afterward with PUT /api/v1/agents/{agent_id}.
Update agent
PUT /api/v1/agents/{agent_id}
Body: AgentUpdateRequest (partial fields). Active agents reject config changes unless force_update=true where supported. Include agent: { "first_response_message": "..." } for the welcome line only, or agent_specific_config for behavior-only updates (merged).
Delete agent
DELETE /api/v1/agents/{agent_id}
2. Provider discovery
| Endpoint | Purpose |
|---|---|
GET /api/v1/providers/model | LLM rows (id, provider_name, model_name, …) |
GET /api/v1/providers/stt | Speech-to-text providers |
GET /api/v1/providers/tts | Text-to-speech providers |
GET /api/v1/providers/vad | VAD providers |
GET /api/v1/providers/all | All of the above in one JSON object |
GET /api/v1/noise-assets | Outbound background noise WAV catalog (id → background_noise_id) |
3. Outbound call initiation
POST /api/call/create_call
This route is not under /api/v1. Authenticate every request with x-api-key (best for integrations and cron jobs) or Authorization: Bearer plus your Supabase session JWT (same token the Kupe web app uses). The API key owner or Bearer user is always who is billed and whose phone_numbers / mappings are used — there is no user_id query parameter.
| Query parameter | Required | Description |
|---|---|---|
phoneNumber | Yes | Destination — use E.164 with country code (e.g. +91… India, +1… US/Canada); URL-encode + as %2B in curl |
agent_id | No* | Agent to run (*recommended for voice agents) |
workflow_id | No | Alternative to agent_id |
phone_number_id | No | Specific outbound line from phone_numbers (must belong to the authenticated user) |
phone_numbers row is resolved (explicit id or via mapping), Kupe uses that row’s configured telephony backend (Twilio, Elison, or Exotel). If no row is found, a fallback may use the first available active line for that user.
Optional header Origin: if present, must pass Kupe’s allow-list (validate_origin). Omit Origin for server-to-server curl.
Examples
US/Canada — API key:
call_session_id as the primary Kupe session id; use request_id to correlate logs and GET /api/v1/call-analytics?request_id=…. telephony_leg_id is an opaque carrier leg id when present. Initiation errors may return HTTP 200 with "status": "failed" and the same ids for tracing.
Failure cases
402— billing / credits (subject user)401— missing or invalidx-api-key/ Bearer403— invalidOrigin500— server/telephony configuration error
4. Knowledge base & files
There is no single public “scrape this URL into the vector DB” REST route for voice agents; URL ingestion exists in other product flows (e.g. text builder over WebSocket). For HTTP integrations, use file upload + agent mapping.Upload file (RAG)
POST /api/v1/files/upload (multipart/form-data)
| Field | Description |
|---|---|
file | Document / audio / etc. (see GET /api/v1/files/supported-formats) |
agent_id | Optional — when set, chunks are tagged for that agent’s retrieval |
List user files
GET /api/v1/files/user/me
Get / update / delete file
GET /api/v1/files/{file_id}PUT /api/v1/files/{file_id}DELETE /api/v1/files/{file_id}
Attach / detach file to agent (mappings)
| Method | Path | Action |
|---|---|---|
POST | /api/v1/agent-files-mappings/create | Body: CreateMappingRequest (agent_id, upload_file_id, …) |
GET | /api/v1/agent-files-mappings/agent/{agent_id} | List files for agent |
DELETE | /api/v1/agent-files-mappings/agent/{agent_id}/file/{upload_file_id} | Detach |
Vector search (optional)
POST /api/v1/search/semantic?agent_id={agent_id} — RAG search scoped to an agent’s documents.
5. Webhooks (post-call)
Kupe does not emit a single fixed “call completed” JSON to your URL unless you configure it:- In the dashboard, open the agent → Post analysis.
- Add structured output and/or an HTTP integration (tool) that runs after the call.
-
Completion-style fields (
call_duration,extracted_variables,summary,full_conversation,recording_url,from_number,to_number,cost,sentiment_analysis) should be modeled as outputs in the structured prompt / JSON schema or as arguments in the HTTP tool schema so your webhook receives exactly those keys. -
Error events — map failed HTTP tool executions or
5xxfrom your endpoint to your “error webhook”; Kupe surfaces tool errors in post-analysis logs.
6. RBAC, API key scopes, and feature flags
RBAC (API keys)
API keys must passRBACMiddleware:
- Paths allowed for the developer role come from
milli_ai_backend/rbac_paths.json(e.g./api/v1/agents,/api/v1/providers,/api/v1/files,/api/v1/agent-files-mappings,/api/v1/search,/api/v1/phone, …). - Rows in
api_key_scopes(allowed_path_prefix,allowed_method) are now evaluated in addition to role prefixes, so narrowly scoped keys work.
request.state.role values now fall back to developer path list so keys stay usable.
Feature flags
If the same user hits the dashboard with a Bearer token,FeatureAccessMiddleware may gate some routes by user_roles.feature_flags. API-key-only requests hit FeatureAccess before the user is attached and therefore bypass that gate for mapped paths—RBAC remains the control for keys.
Live verification (2026-04-23)
Commands run from a developer machine against production:| Request | HTTP | Body (abridged) |
|---|---|---|
GET /health | 200 | {"status":"healthy","service":"milli-ai-backend",...} |
GET /api/v1/providers/model (with x-api-key) | 403 | {"detail":"Forbidden: API Key does not have required scopes"} |
GET /api/v1/agents/summary (with x-api-key) | 403 | same |
rbac_middleware.py, expanded rbac_paths.json), re-run the same curls: you should receive 200 and JSON provider/agent payloads.
Changelog (this repo)
- RBAC: API keys honor
api_key_scopesrows; unknown roles fall back to developer paths; developer path includes providers, agent-files-mappings, search, phone. - Agents: Prefer
model_name,tts_model_name,transcriber_model_nameonPUT /api/v1/agents/{agent_id}; legacymodel_provider_id,tts_provider_id, and STTprovider_idstill accept the same catalog codes or row UUIDs.