> ## Documentation Index
> Fetch the complete documentation index at: https://kupe.in/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice Agent HTTP API

> REST reference for agents, providers, outbound calls, knowledge files, and post-call webhooks (Kupe platform).

<Note>
  For **Mintlify-style interactive docs** (grouped endpoints, **Try it**, request/response panels), open the **API reference** tab — start at [Introduction](/docs/api-reference/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.
</Note>

## 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):

```http theme={null}
x-api-key: <YOUR_KUPE_API_KEY>
```

The key is tied to a **user account** in Kupe (same as dashboard login). For protected `/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:

```http theme={null}
Authorization: Bearer <SUPABASE_JWT>
```

**`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 against `https://api.kupe.in` on **2026-04-23** (see [Live verification](#live-verification-2026-04-23) below).

| Variable | Value                                  |
| -------- | -------------------------------------- |
| API key  | `N8WJOk0TSvUGswbCk8EcaZQh6Symt0mQ`     |
| User ID  | `fbbf7286-4679-4bc1-983a-6d09a048be12` |

Treat API keys like passwords: **this page embeds a real key for integration testing**—rotate it in the Kupe dashboard after you finish QA, and prefer environment variables in shipped clients.

***

## 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](#incoming-calls-pstn--map-a-line-to-an-assistant)) 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](#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](#5-webhooks-post-call)                                                                                                     |
| **context\_params**                | Optional JSON body on `POST /api/call/create_call`: `{ "variable_values": { "name": "Alex", "balance": "$50" } }` replaces `{{name}}`, `{{balance}}` in the agent prompt and first response at dial time.                            |

***

## 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).

1. **Register the line** under your account (if it is not already). Use the **`/api/v1/phone`** routes in [Platform REST APIs](/docs/api-reference/platform-rest-apis) and the **Incoming calls** group in the OpenAPI sidebar (**Try it** on **`POST /api/v1/phone/mapping`**).
2. **Create the mapping** with **`POST https://api.kupe.in/api/v1/phone/mapping`**:

```json theme={null}
{
  "agent_id": "<your-agent-uuid>",
  "phone_number_id": "<phone-row-uuid>"
}
```

* Send exactly one of **`agent_id`** or **`workflow_id`**.
* Authenticate with **`x-api-key`** or **Bearer** JWT. You do not need **`webhook_url`**, **`config_source`**, or **`user_id`** in the body; the server fills those from deployment defaults and the authenticated user when they are omitted.

**Inspect:** **`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. |

After **`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](/docs/assistants/model-providers) and [Supported models & providers](/docs/supported-models).

### Agent-specific configuration

Per-agent runtime behavior lives in **`agent_specific_config`** (stored in `agent_specific_configs.configuration`). You can set it on:

* **`POST /api/v1/agents`** — include `agent_specific_config` in 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.

Read current values from **`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**](#list-background-noise-assets). Empty = none. |
| `background_noise_volume`            | integer (0–100)   | Gain for outbound background ambience (`100` = full, `0` = mute). Default `100`.                           |

Set **`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**

```json theme={null}
POST /api/v1/agents
{
  "agent": { "name": "Support", "first_response_message": "Hello, how can I help?" },
  "agent_model_config": { "model_name": "gpt-4.1-mini", "system_prompt": "You are support." },
  "agent_specific_config": {
    "session_timeout": 300,
    "auto_terminate_call": true,
    "llm_conversation_stop_enabled": true,
    "timezone": "Asia/Kolkata"
  }
}
```

**Example — update behavior only**

```json theme={null}
PUT /api/v1/agents/{agent_id}
{
  "agent_specific_config": {
    "auto_terminate_call": true,
    "end_of_speech_timeout": 1.2
  }
}
```

OpenAPI schema: **`AgentSpecificConfigInput`** in the [API reference](/docs/api-reference/introduction).

### 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).                                                            |

An empty array `[]` 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_id` from `agent_specific_config`.
* **Update (clear):** `"background_noise_id": null` or `"background_noise_id": ""`.
* **Verify:** `GET /api/v1/agents/{agent_id}` → `configurations.agent_specific_config` should not contain a non-empty `background_noise_id`.
* **Dashboard:** choose **None** under Background noise asset (same as clearing via API).
* **Mute only:** keep an `id` but set `"background_noise_volume": 0`.

**Example workflow**

```http theme={null}
GET /api/v1/noise-assets
x-api-key: <YOUR_KUPE_API_KEY>
```

```json theme={null}
PUT /api/v1/agents/{agent_id}
{
  "agent": { "apply_noise_reduction": true },
  "agent_specific_config": {
    "background_noise_id": "office.wav",
    "background_noise_volume": 40,
    "noise_cancellation_strength": 50,
    "time_to_cut": 30,
    "message_of_cut": "We will need to end the call shortly.",
    "end_call_message_mode": "fixed"
  }
}
```

Interactive docs: **Noise Assets** → **List available background noise assets** in the [API reference](/docs/api-reference/introduction).

### 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`) |

Use these lists to populate UI or to resolve **codes** vs **UUIDs** for agent creation.

***

## 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 parameters

| 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)                                     |

### Optional JSON body — prompt variables

Send `Content-Type: application/json` with a **`variable_values`** object to personalize the call at dial time. Each key replaces a matching `{{key}}` placeholder in the agent **system prompt** and **first-response message** (same behavior as campaign/batch calls, without creating a campaign).

Example agent prompt: `Hello {{name}}, I'm calling about your {{balance}} balance at {{company}}.`

```json theme={null}
{
  "variable_values": {
    "name": "Alex",
    "balance": "$50",
    "company": "Acme Corp"
  }
}
```

Variables are stored on the call session and applied when the telephony webhook connects the media stream.

When a **`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**:

```bash theme={null}
curl -sS -X POST \
  -H "x-api-key: <YOUR_KUPE_API_KEY>" \
  'https://api.kupe.in/api/call/create_call?phoneNumber=%2B15551234567&agent_id=<AGENT_UUID>'
```

India — **Bearer** session (with prompt variables):

```bash theme={null}
curl -sS -X POST \
  -H "Authorization: Bearer <SUPABASE_JWT>" \
  -H "Content-Type: application/json" \
  -d '{"variable_values":{"name":"Alex","balance":"$50"}}' \
  'https://api.kupe.in/api/call/create_call?phoneNumber=%2B919876543210&agent_id=<AGENT_UUID>'
```

**Typical success JSON**

```json theme={null}
{
  "status": "success",
  "request_id": "<UUID>",
  "call_session_id": "<UUID>",
  "user_id": "<SUBJECT_USER_UUID>",
  "telephony_provider": "pstn",
  "target_number": "+15551234567",
  "from_phone_number": "+1xxxxxxxxxx",
  "telephony_leg_id": "<opaque-leg-id>"
}
```

Use **`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 invalid `x-api-key` / Bearer
* `403` — invalid `Origin`
* `500` — 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 (outbound integration + post-call)

### Outbound call loop

1. **Start call (your CRM → Kupe)** — `POST /api/call/create_call` with `x-api-key`, **or** signed `POST /api/v1/webhooks/agents/{agent_id}/incoming`.
2. **Call ends (Kupe → your CRM)** — if the agent has **outgoing webhook** enabled and the web/phone toggles match, Kupe POSTs the lead payload to your URL.

See [Agent webhooks](/docs/api-reference/endpoint/webhook) for curl examples, HMAC signing, and config API (`GET`/`PUT /api/v1/webhooks/agents/{agent_id}`, `GET /api/v1/webhooks/me`).

Agent toggles live under **Advance → Webhooks** (`agent_specific_config.webhooks`):

* `trigger_on_web_call` / `trigger_on_phone_call`
* `incoming.enabled` + server-managed HMAC `secret`
* `outgoing.url` / `method` / `headers` / `auth` / `retry_count`

### System default lead schema

Every ended call also runs the **system** post-call analysis (no rule attachment required). Fields include lead info (`customer_name`, `mobile_number`, …), call details, and AI qualification (`lead_status`, `lead_temperature`, `lead_score`, `ai_summary`, next actions). Legacy keys (`call_summary`, `user_name`, …) remain as compat aliases.

### Custom post-analysis rules (optional)

You can still attach dashboard **Post analysis** rules / HTTP tools. Those run **in addition** to the system default and outgoing webhook dispatch.

***

## 6. RBAC, API key scopes, and feature flags

### RBAC (API keys)

API keys must pass `RBACMiddleware`:

* 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.

Unknown `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:

```bash theme={null}
API_KEY='N8WJOk0TSvUGswbCk8EcaZQh6Symt0mQ'
curl -sS -H "x-api-key: $API_KEY" 'https://api.kupe.in/api/v1/providers/model'
curl -sS -H "x-api-key: $API_KEY" 'https://api.kupe.in/api/v1/agents/summary?page=1&page_size=3'
```

**Observed**

| 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                                                            |

So at verification time, **authenticated REST routes failed RBAC** for this key. After deploying this repository’s updates (`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_scopes` rows; 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_name`** on **`PUT /api/v1/agents/{agent_id}`**; legacy **`model_provider_id`**, **`tts_provider_id`**, and STT **`provider_id`** still accept the same catalog **codes** or row UUIDs.
