# Agent reference

Every field an agent accepts, what it changes, and what it defaults to. An agent is the configuration a call runs under; nothing here is per-call except where it says so. Create with `POST /v1/agents`, change with `PATCH` — every field is optional on a patch.

## Creating one

`POST /v1/agents`

**Shell**

```bash
curl -s -X POST https://voice.sphoro.com/v1/agents \
  -H "Authorization: Bearer $SPHORO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Clinic reception",
    "languages": [
      {
        "code": "en-IN",
        "greeting": "Thanks for calling Acme Clinic. How can I help?",
        "system_prompt": "You are the receptionist for Acme Clinic. Book, move and cancel appointments. Never give medical advice; offer to take a message for a doctor instead."
      }
    ],
    "temperature": 0.4,
    "max_tokens": 400
  }'
```

**Response201 Created**

```json
{
  "id": "agt_9f2c1a7d4b6e803f",
  "name": "Acme Clinic reception",
  "mode": "conversation",
  "autonomy": "agentic",
  "model": "claude-sonnet-5",
  "temperature": 0.4,
  "max_tokens": 400,
  "languages": [
    {
      "code": "en-IN",
      "greeting": "Thanks for calling Acme Clinic. How can I help?",
      "system_prompt": "You are the receptionist for Acme Clinic…"
    }
  ],
  "created_at": "2026-09-08T09:14:22Z"
}
```

> **An unknown field is refused, not ignored.** A typo in a field name comes back as a `400` naming the field. This is deliberate: silently dropping `temprature` means an agent runs at a temperature nobody chose, and the first sign of it is a call that sounds wrong weeks later.

## Identity and model

| Field | Type | Description |
| --- | --- | --- |
| `name` required | string | What you call it. Max 120 characters, and not required to be unique — which is why your code should hold the `id`. |
| `description` optional | string | For your own bookkeeping. Never spoken, never shown to a caller. |
| `model` optional | string | The language model. Defaults to `claude-sonnet-5`. A reasoning model cannot answer a telephone — see [latency](https://voice.sphoro.com/docs/latency). |
| `models` optional | object | The model to use on each specific vendor, keyed by vendor name — `{"anthropic": "claude-sonnet-5", "gemini": "gemini-2.0-flash"}`. Read when the chain falls through to a vendor that does not have the model named above. Replaces the whole map. |
| `temperature` optional | number, 0–2 | Defaults to `0.6`. Low for reciting a policy, higher for open conversation. `0` is a legitimate setting and is not treated as "unset". |
| `max_tokens` optional | integer, 1–4000 | Defaults to `512`. This is a phone call: a long reply is one the caller talks over, so treat a high value as a bug rather than a feature. |
| `voice_engine` optional | "cascaded" \| "realtime" | Defaults to `cascaded` — a recognizer, a model and a voice in a row, which works on every deployment. `realtime` is one model that listens and replies directly in speech, owning its own turn-taking. Setting it on a deployment with no realtime vendor is **refused** rather than saved, because such an agent would answer the call in silence. |
| `timezone` optional | string, IANA | Which clock this agent's calls run against — `"Asia/Kolkata"`. Read by `{{current_date}}`, `{{current_time}}` and calling-hours enforcement. Empty uses the deployment's own. |
| `metadata` optional | object | String keys to string values, stored and returned untouched. Yours. |

## What it says, and in which language

There is no `system_prompt`, `language`, `voice_id` or `greeting` on the agent itself. All four live on an entry in `languages`, because an agent that speaks three languages needs three of each.

| Field | Type | Description |
| --- | --- | --- |
| `languages` required | array | Every language the agent speaks, **the one calls open in first**. Required on any agent a model answers. Replaces the whole list — a save that omits the prompt would take it away, so that save is refused. Entry shape below. |
| `language_detection` optional | "off" or "follow" | Defaults to `follow` once the agent has more than one language. `follow` moves the agent into whichever of its languages the caller speaks — voice, prompt and all; `off` keeps every call in the language it opened in. Meaningless with one language. |
| `speed` optional | number, 0.25–4 | Defaults to `1`. A multiplier on whichever voice is speaking, in every language. |
| `pitch` optional | number, 0.25–4 | Defaults to `1`. |
| `filler_phrase` optional | string | Spoken while a tool runs, so a slow lookup is not dead air. Does not make anything faster. |

### A language entry

**JSON**

```json
{
  "code": "hi",
  "voice_id": "hindi-voice",
  "voices": { "elevenlabs": "…", "cartesia": "…" },
  "tts_provider": "cartesia",
  "stt_provider": "gladia",
  "system_prompt": "आप एक्मे क्लिनिक की रिसेप्शनिस्ट हैं…",
  "greeting": "नमस्ते, एक्मे क्लिनिक।",
  "silence_prompt": "क्या आप अब भी लाइन पर हैं?",
  "final_message": "आपका दिन शुभ हो।",
  "keywords": ["एक्मे", "डॉक्टर शर्मा"]
}
```

| Field | Type | Description |
| --- | --- | --- |
| `code` required | string | The language. Every code this deployment accepts is listed on [language and voice](https://voice.sphoro.com/docs/voices). |
| `system_prompt` optional | string | This language's instructions, max 100,000 characters. Required on the first entry — that one is the agent's prompt. A later entry may leave it empty and derive one from the first. |
| `greeting` optional | string | The first thing said on a call that opens in this language. Interruptible, like any other turn. |
| `voice_id` optional | string | The voice, for whichever vendor the chain settles on. Every entry after the first needs one; the first may leave it empty and take the deployment default. |
| `voices` optional | object | One voice id per vendor. Voice ids are not portable between vendors, so this is what keeps an agent sounding the same when the chain falls through to its second choice. |
| `tts_provider` optional | string | Pin the voice vendor for this language. Unset lets the chain decide. See [vendors](https://voice.sphoro.com/docs/providers). |
| `stt_provider` optional | string | Pin the transcription vendor for this language — worth doing where one vendor is markedly better at the accent. |
| `silence_prompt` optional | string | What "are you still there?" says in this language. |
| `final_message` optional | string | The goodbye, in this language. |
| `keywords` optional | string[] | Words to bias transcription towards — product names, place names, the doctor's surname. The cheapest fix for a name that keeps coming back misheard. |

## What it can do

| Field | Type | Description |
| --- | --- | --- |
| `mode` optional | "conversation" \| "keypad" \| "normal" \| "webrtc" | Defaults to `conversation`. Only the first uses speech recognition or a model. See [kinds of agent](https://voice.sphoro.com/docs/agent-types). |
| `autonomy` optional | "agentic" \| "simple" \| "custom" | Defaults to `agentic`. Who holds this end of the call. Read only by a `conversation` agent. |
| `capabilities` optional | object | The switches behind `custom`: `search_knowledge`, `call_functions`, `transfer`, `end_call`, `max_tool_rounds` (1–10, `0` for the default of 3) and `retrieval_timeout_ms` (50–2000, `0` for the default). Stored on every agent, read only under `custom`. Replaces the whole block. |
| `tools` optional | string[] | Function names, each of which must be registered. `GET /v1/functions` lists them. See [tools and functions](https://voice.sphoro.com/docs/functions). |
| `tool_configs` optional | object[] | The tools this agent carries itself, as opposed to `tools` above, which names functions the deployment registered. Each entry has a `kind` — `calendar_availability`, `book_appointment`, `transfer_call` or `custom_function` — a `name` the model calls, a `description` it decides *when* to call from, an optional `pre_tool_message` keyed by language code, and one configuration block matching the kind. Replaces the whole list. A credential inside one is written as `{"value": "…"}` and comes back as `{"hint": "••••1234"}`; leaving it out on an edit keeps what is stored, which is how a description is changed without resending an API key. See [tools and functions](https://voice.sphoro.com/docs/functions). |
| `knowledge_base_ids` optional | string[] | Every base this agent searches, searched together on each turn. Attaching at least one is what gives the agent the `search_knowledge_base` tool — there is no separate switch, and naming that tool in `tools` does nothing. Send `[]` to detach every base. |
| `knowledge_base_id` optional | string | The older single-base field. Still accepted and returned, folded into the list above. |
| `flow` optional | object | A conversation flow: `start`, `nodes[]`, and `edges[]` on each node. Validated in full, with every problem reported at once. Send `{}` to take the flow away. See [flows](https://voice.sphoro.com/docs/flows). |
| `keypad` optional | object | Digits to what the agent says — `{"1": {"say": "…", "end_call": true}}`, or `"transfer": true` to put the caller through. Answered without the model, in either mode. See [keypad and IVR](https://voice.sphoro.com/docs/keypad). |

## How it listens, and when it gives up

| Field | Type | Description |
| --- | --- | --- |
| `conversation` optional | object | Barge-in, silence and duration. Every value of `0` means "the default". Replaces the whole block. Full breakdown on [conversation behaviour](https://voice.sphoro.com/docs/behaviours). |
| `no_match` optional | string | Spoken by a keypad agent when the caller presses something with no entry. Three in a row ends the call. |
| `no_input` optional | string | Spoken by a keypad agent when nothing is pressed. |
| `max_no_input` optional | integer | How many times a keypad agent re-reads its menu to a silent caller before giving up. |
| `keypad_timeout_seconds` optional | integer | How long a keypad agent waits for a press before treating it as silence. |
| `ring_seconds` optional | integer, 1–120 | `0` means the default of 30. How long a call rings before nobody has answered — read on the two shapes that ring rather than answer, a `webrtc` agent and a `simple` one taking an inbound call. Ignored elsewhere: nothing rings when the agent itself picks up. |

## Telephone, recording and afterwards

| Field | Type | Description |
| --- | --- | --- |
| `carrier` optional | string | Which carrier this agent dials through. Unset uses the deployment's default. See [numbers and carriers](https://voice.sphoro.com/docs/telephony). |
| `transfer_number` optional | string, E.164 | Setting it gives the agent the `transfer_call` tool — and this is the **only** number that tool can ever dial. The model chooses when to hand off, never where. On a `normal` agent it is where every caller goes, with nothing deciding. |
| `record_calls` optional | boolean | Defaults to `false`. Needs recording storage configured on the deployment as well. See [recordings](https://voice.sphoro.com/docs/recordings). |
| `transcribe_calls` optional | boolean | Transcribe a `webrtc` agent's calls — the two people on them. Ignored by every other mode, which is transcribed anyway because the recognizer is how the agent hears the caller at all. |
| `post_call` optional | object | Defaults to `{"summary": true}`. What is worked out from the transcript once the call ends, including typed extractions. Replaces the whole block. See [post-call analysis](https://voice.sphoro.com/docs/post-call). |
| `webhook_url` optional | string | Overrides your account's endpoints for this agent's events. HTTPS only. See [webhooks](https://voice.sphoro.com/docs/webhooks). |

## Grouping and cost

| Field | Type | Description |
| --- | --- | --- |
| `project_id` optional | string | The cost centre this agent's calls are attributed to. See [projects](https://voice.sphoro.com/docs/projects). |
| `group_id` optional | string | The agent group this agent belongs to. Its defaults fill in every inheritable field this agent has not claimed in `overrides`. See [agent groups](https://voice.sphoro.com/docs/agent-groups). |
| `overrides` optional | string[] | The inheritable fields whose value on *this* agent wins over its group's. Set for you when the agent joins a group. |
| `template` optional | string | Create from a template instead of from nothing. Accepted on create only. See [agent templates](https://voice.sphoro.com/docs/templates). |
| `persona_id` optional | string | The persona this agent deploys. The persona owns the prompt, voice, knowledge and flow; the agent owns the telephone. See [personas](https://voice.sphoro.com/docs/personas). |

## Replacing versus merging

Scalars merge; lists and blocks replace. That is one rule with one consequence worth stating plainly, because it is the difference between adding a language and losing two.

| Sending | Does |
| --- | --- |
| `{"temperature": 0.2}` | Changes the temperature. Nothing else moves. |
| `{"languages": [ … one entry … ]}` | Makes that the **only** language. Read the current list, append, send the whole thing. |

The blocks that replace whole: `languages`, `keypad`, `models`, `capabilities`, `conversation`, `post_call`, `flow`, `tools`, `knowledge_base_ids` and `overrides`.

**Shell**

```bash
# Add a language without losing the ones already there.
CURRENT=$(curl -s https://voice.sphoro.com/v1/agents/$AGENT_ID \
  -H "Authorization: Bearer $SPHORO_API_KEY")

echo "$CURRENT" | jq '{languages: (.languages + [{code:"ta", voice_id:"tamil-voice", greeting:"வணக்கம்."}])}' \
  | curl -s -X PATCH https://voice.sphoro.com/v1/agents/$AGENT_ID \
      -H "Authorization: Bearer $SPHORO_API_KEY" \
      -H "Content-Type: application/json" -d @-
```

## Listing, duplicating and deleting

`GET /v1/agents`

`POST /v1/agents/{id}/duplicate`

`DELETE /v1/agents/{id}`

`POST /v1/agents/{id}/restore`

Deleting is soft: the agent stops being listed and stops taking calls, and its finished calls keep their transcripts and analysis. `restore` brings it back with the same id, which is what you want after somebody deletes the production agent on a Friday. Duplicating is the right way to try a prompt change against real traffic without editing the live one.

> **One agent per use case, not per customer.** Agents are cheap to create and free to keep, but per-customer variation belongs in [call variables](https://voice.sphoro.com/docs/variables), not in ten thousand agents. The values you would have baked in are exactly what `call_variables` is for, and one agent means one prompt to fix when it is wrong.

## Trying it before a caller does

Two things worth doing before an agent takes real traffic, both of which cost nothing:

- **Place a browser call to it.** No number needed — see [talk from a browser](https://voice.sphoro.com/docs/browser).
- **Run the post-call pass on a transcript you paste in**, with `POST /v1/agents/{id}/post_call/test`, so you find out that an extraction never fires without placing thirty calls to discover it. See [post-call analysis](https://voice.sphoro.com/docs/post-call).

Sphoro Voice also ships a set of working agents to start from rather than an empty form — see [agent templates](https://voice.sphoro.com/docs/templates).
