sphoroVOICEdocs
Markdown

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

FieldTypeDescription
name
required
stringWhat you call it. Max 120 characters, and not required to be unique — which is why your code should hold the id.
description
optional
stringFor your own bookkeeping. Never spoken, never shown to a caller.
model
optional
stringThe language model. Defaults to claude-sonnet-5. A reasoning model cannot answer a telephone — see latency.
models
optional
objectThe 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–2Defaults 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–4000Defaults 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, IANAWhich 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
objectString 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.

FieldTypeDescription
languages
required
arrayEvery 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–4Defaults to 1. A multiplier on whichever voice is speaking, in every language.
pitch
optional
number, 0.25–4Defaults to 1.
filler_phrase
optional
stringSpoken while a tool runs, so a slow lookup is not dead air. Does not make anything faster.

A language entry

JSON
{
  "code": "hi",
  "voice_id": "hindi-voice",
  "voices": { "elevenlabs": "…", "cartesia": "…" },
  "tts_provider": "cartesia",
  "stt_provider": "gladia",
  "system_prompt": "आप एक्मे क्लिनिक की रिसेप्शनिस्ट हैं…",
  "greeting": "नमस्ते, एक्मे क्लिनिक।",
  "silence_prompt": "क्या आप अब भी लाइन पर हैं?",
  "final_message": "आपका दिन शुभ हो।",
  "keywords": ["एक्मे", "डॉक्टर शर्मा"]
}
FieldTypeDescription
code
required
stringThe language. Every code this deployment accepts is listed on language and voice.
system_prompt
optional
stringThis 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
stringThe first thing said on a call that opens in this language. Interruptible, like any other turn.
voice_id
optional
stringThe 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
objectOne 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
stringPin the voice vendor for this language. Unset lets the chain decide. See vendors.
stt_provider
optional
stringPin the transcription vendor for this language — worth doing where one vendor is markedly better at the accent.
silence_prompt
optional
stringWhat "are you still there?" says in this language.
final_message
optional
stringThe 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

FieldTypeDescription
mode
optional
"conversation" | "keypad" | "normal" | "webrtc"Defaults to conversation. Only the first uses speech recognition or a model. See kinds of agent.
autonomy
optional
"agentic" | "simple" | "custom"Defaults to agentic. Who holds this end of the call. Read only by a conversation agent.
capabilities
optional
objectThe 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.
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 kindcalendar_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.
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
stringThe older single-base field. Still accepted and returned, folded into the list above.
flow
optional
objectA 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.
keypad
optional
objectDigits 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.

How it listens, and when it gives up

FieldTypeDescription
conversation
optional
objectBarge-in, silence and duration. Every value of 0 means "the default". Replaces the whole block. Full breakdown on conversation behaviour.
no_match
optional
stringSpoken by a keypad agent when the caller presses something with no entry. Three in a row ends the call.
no_input
optional
stringSpoken by a keypad agent when nothing is pressed.
max_no_input
optional
integerHow many times a keypad agent re-reads its menu to a silent caller before giving up.
keypad_timeout_seconds
optional
integerHow long a keypad agent waits for a press before treating it as silence.
ring_seconds
optional
integer, 1–1200 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

FieldTypeDescription
carrier
optional
stringWhich carrier this agent dials through. Unset uses the deployment's default. See numbers and carriers.
transfer_number
optional
string, E.164Setting 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
booleanDefaults to false. Needs recording storage configured on the deployment as well. See recordings.
transcribe_calls
optional
booleanTranscribe 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
objectDefaults 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.
webhook_url
optional
stringOverrides your account's endpoints for this agent's events. HTTPS only. See webhooks.

Grouping and cost

FieldTypeDescription
project_id
optional
stringThe cost centre this agent's calls are attributed to. See projects.
group_id
optional
stringThe agent group this agent belongs to. Its defaults fill in every inheritable field this agent has not claimed in overrides. See 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
stringCreate from a template instead of from nothing. Accepted on create only. See agent templates.
persona_id
optional
stringThe persona this agent deploys. The persona owns the prompt, voice, knowledge and flow; the agent owns the telephone. See 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.

SendingDoes
{"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
# 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, 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.
  • 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.

Sphoro Voice also ships a set of working agents to start from rather than an empty form — see agent templates.