curl -X PUT https://api.telzino.com/v1/agents/123e4567-e89b-12d3-a456-426614174000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ragEnabled": true,
"knowledgeBaseId": "11111111-1111-1111-1111-111111111111",
"ragConfig": {
"rewrite_query": true,
"top_k": 5
}
}'
{
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"organization_id": "987fcdeb-51a2-3bc4-d567-890123456789",
"name": "Updated Agent Name",
"description": "Handles customer inquiries",
"greeting_message": "Hello! How can I help you today?",
"system_prompt": "Updated system prompt...",
"agent_type": "simple",
"model": "gpt-4.1",
"voice_model": "gpt-4o-realtime-preview",
"stt_model": "deepgram",
"tts_model": "cartesia",
"voice_settings": {"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02", "speed": 1.1},
"config": {},
"extensions": {},
"integrations": {},
"transfer_enabled": true,
"transfer_rules": "Transfer to +15559876543 for technical issues",
"language": "multi",
"timezone": "Europe/London",
"enable_recording": false,
"echo_detection": false,
"call_detection": false,
"knowledgebase_links": ["https://example.com/updated-faq"],
"email": null,
"email_tool_enabled": false,
"email_server_id": null,
"pronunciations": [],
"status": "active",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:20:00Z"
}
}
{
"error": "Invalid request data",
"details": [
{
"code": "invalid_enum_value",
"message": "Invalid enum value. Expected 'simple' | 'advanced' | 'custom' | 'appointment_scheduler' | 'receptionist' | 'message_taker' | 'blank_slate' | 'outbound', received 'invalid'",
"path": ["agentType"]
}
]
}
{
"error": "forbidden",
"error_description": "Access denied"
}
{
"error": "forbidden",
"error_description": "Model not available for this organization: claude-sonnet-4-6"
}
{
"error": "forbidden",
"error_description": "TTS provider not available for this organization: minimax"
}
{
"error": "forbidden",
"error_description": "Feature(s) not available for this organization: listings_search"
}
{
"error": "forbidden",
"error_description": "Screenshare requires a model that can read images. Choose a different model, or turn screenshare off."
}
{
"error": "forbidden",
"error_description": "Screenshare is not available for healthcare organizations."
}
{
"error": "not_found",
"error_description": "Agent not found or access denied"
}
Agents
Update Agent
Updates an existing agent
PUT
/
v1
/
agents
/
{agentId}
curl -X PUT https://api.telzino.com/v1/agents/123e4567-e89b-12d3-a456-426614174000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ragEnabled": true,
"knowledgeBaseId": "11111111-1111-1111-1111-111111111111",
"ragConfig": {
"rewrite_query": true,
"top_k": 5
}
}'
{
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"organization_id": "987fcdeb-51a2-3bc4-d567-890123456789",
"name": "Updated Agent Name",
"description": "Handles customer inquiries",
"greeting_message": "Hello! How can I help you today?",
"system_prompt": "Updated system prompt...",
"agent_type": "simple",
"model": "gpt-4.1",
"voice_model": "gpt-4o-realtime-preview",
"stt_model": "deepgram",
"tts_model": "cartesia",
"voice_settings": {"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02", "speed": 1.1},
"config": {},
"extensions": {},
"integrations": {},
"transfer_enabled": true,
"transfer_rules": "Transfer to +15559876543 for technical issues",
"language": "multi",
"timezone": "Europe/London",
"enable_recording": false,
"echo_detection": false,
"call_detection": false,
"knowledgebase_links": ["https://example.com/updated-faq"],
"email": null,
"email_tool_enabled": false,
"email_server_id": null,
"pronunciations": [],
"status": "active",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:20:00Z"
}
}
{
"error": "Invalid request data",
"details": [
{
"code": "invalid_enum_value",
"message": "Invalid enum value. Expected 'simple' | 'advanced' | 'custom' | 'appointment_scheduler' | 'receptionist' | 'message_taker' | 'blank_slate' | 'outbound', received 'invalid'",
"path": ["agentType"]
}
]
}
{
"error": "forbidden",
"error_description": "Access denied"
}
{
"error": "forbidden",
"error_description": "Model not available for this organization: claude-sonnet-4-6"
}
{
"error": "forbidden",
"error_description": "TTS provider not available for this organization: minimax"
}
{
"error": "forbidden",
"error_description": "Feature(s) not available for this organization: listings_search"
}
{
"error": "forbidden",
"error_description": "Screenshare requires a model that can read images. Choose a different model, or turn screenshare off."
}
{
"error": "forbidden",
"error_description": "Screenshare is not available for healthcare organizations."
}
{
"error": "not_found",
"error_description": "Agent not found or access denied"
}
Path Parameters
string
required
The unique identifier of the agent (UUID format)
Request Body
All fields are optional. Only include the fields you want to update.Reads and writes use different field names.
GET /v1/agents/{id} returns the stored row with snake_case top-level keys (for example system_prompt), while this endpoint takes the camelCase names documented below (for example systemPrompt). Rename the top-level keys before sending a read back — see reservationConfig below for what happens otherwise; keys inside nested objects such as voiceSettings and config stay snake_case. email also changes shape: send it as an array of addresses, but GET returns it as the stored string.string
Optional organization guard (UUID). When provided, the request is rejected with
404 unless the target agent already belongs to this organization. It does not move the agent between organizations — it only scopes the update so you cannot accidentally modify an agent in a sibling organization under the same account. Omit it to keep the default account-scoped behavior.Basic Information
string
Display name of the agent (1-255 characters)
string
Description of what the agent does
string
Initial message the agent says when call starts
string
Greeting spoken when this agent places an outbound call (see Outbound Calls)
string
System prompt defining agent behavior and personality
string
Type of agent configuration.Supported values:
simple, advanced, custom, appointment_scheduler, receptionist, message_taker, blank_slate, outbound, opentable_reservation, translatorobject
Restaurant settings for an
opentable_reservation agent, in the shape documented on
Create Agent. Ignored by every other agent type.Sent as a whole object: the value replaces the stored settings rather than merging into them, so read the agent first and send back the full object with your change applied. Omitting the field leaves the stored settings untouched.The read and the write use different key names. The same asymmetry applies to
GET /v1/agents/{id} returns the stored row, where this object is called reservation_config (snake_case). This endpoint accepts only reservationConfig (camelCase), and rejects any top-level key it does not recognise.A literal read-modify-write therefore fails with 400 "Invalid request data", with a details entry whose code is unrecognized_keys, and nothing is saved:// ❌ read back from GET, sent straight to PUT — rejected with 400 (unrecognized_keys)
{ "reservation_config": { "max_party_size": 12 } }
// ✅ renamed for the write
{ "reservationConfig": { "max_party_size": 12 } }
amd_config / amdConfig.Changing
restaurant_url without also updating restaurant_id leaves the agent checking the previous restaurant’s availability and texting a link that books a table there. Resolve the new URL to its ID and send both together.object
Interpreter settings for a
translator agent, in the shape and with the rules documented on
Create Agent. Validated and stored whenever sent, on any agent type; it only has an effect on a translator agent.The object is sent whole: its value replaces the stored settings rather than merging into them, and it is validated as a complete configuration. Read the agent first, then send back the full object with your change applied. Omitting interpreterConfig leaves the stored settings untouched.Sending interpreterConfig: null clears the stored settings on an agent that is not a translator. On a translator agent (or in a request that changes agentType to translator), null is rejected with required, at path: ["interpreterConfig"].- Changing
agentTypetotranslatorneeds valid settings, either in this request or already stored on the agent; if no settings are sent and none are stored, the request is rejected withrequired, atpath: ["interpreterConfig"]. Settings that break a rule are rejected with that rule’s code, at that setting’s own path. - Changing
agentTypetotranslatoralso setsdetached: trueon the agent, the same as creating a translator does. Re-sendingtranslatorfor an agent that already is one leavesdetachedas it is. - On a
translatoragent, and in a request that changesagentTypetotranslator, the on/off settings a translator does not use are stored off whatever the request sends and whatever the agent had stored:ragEnabled,transferEnabled,warmTransferEnabled,webTransferEnabled,emailToolEnabled,echoDetection,detectAutomatedCallers,callDetection,screenshareEnabled, andbackground_audio_enabled,thinking_sounds_enabledandresponse_filler_enabledinsideconfig(its other keys are kept). AMobile_Call_Detectiontool inextensions.toolsis dropped; other tools are kept. - Changing
agentTypeaway fromtranslatorleaves the stored settings untouched and unused, and those on/off settings stay off until you turn them on. - Switching to
translatoron this endpoint gets none of the other template defaults a POST applies. If the agent’s storedttsModelis not alreadycartesia, sendttsModel: "cartesia"in the same request, or it is rejected withvoice_provider. - On a
translatoragent, when this request sendsvoiceSettingsorttsModel, or switches the agent totranslator, a Cartesia voice model (voiceSettings.model) that is missing or outside thesonic-3family (sonic-3orsonic-3.6; anything else, for example the retiredsonic-2orsonic-3.5) is set tosonic-3(and a missingvoice_idgets the template voice), rather than the request being rejected. - A translator agent’s
ttsModelmust becartesia; any other value is rejected withvoice_provider, atpath: ["ttsModel"]— not underinterpreterConfig, because it validatesttsModel, not the interpreter settings. - On a
translatoragent,languageis set to the Party A language whenever the agent’s interpreter settings are valid (theinterpreterConfigin this request, or else the stored settings), even when the request also sends its ownlanguage. For an agent that is already a translator, if nointerpreterConfigis sent and the stored settings no longer pass validation,languageis not changed by them, and the request’s ownlanguage(if any) is saved. Switching an agent totranslatorwith stored settings that no longer pass validation is rejected instead, as above.
The read and the write use different key names.
GET /v1/agents/{id} returns this object as interpreter_config (snake_case). This endpoint accepts only interpreterConfig (camelCase) and rejects any top-level key it does not recognise, so sending interpreter_config back fails with 400 "Invalid request data", with a details entry whose code is unrecognized_keys. The keys inside the object are snake_case in both directions.string
Agent activation status.Supported values:
active- Agent is available to receive callsinactive- Agent is temporarily disabledcancelled- Agent is permanently disabledpending- Agent is pending activation
active and inactive on their own, use Update Agent Status — it takes just this field, so there is no risk of overwriting other configuration.AI/Voice Model Configuration
string
AI model used for the agent. See List LLM Models for the full list with descriptions.Supported values:
gpt-4.1 (recommended, can never be locked out), gpt-4o, gpt-4o-mini, gpt-4.1-mini, gpt-4.1-nano, grok-3-fast, moonshotai/Kimi-K2.6, google/gemma-4-31b-it, claude-sonnet-4-6A Partner or Reseller can lock individual models for an organization via
Feature Administration. Switching
model to one that is locked is rejected with 403 forbidden — but re-sending the
agent’s current model always succeeds even if it has since become locked, so a plain
read-modify-write round trip never breaks and an agent already on a since-locked model
keeps running.string
Voice model for real-time speech
string
Speech-to-text model.Supported values:
deepgramstring
Text-to-speech model.Supported values:
cartesia, minimax, inworldWhen Feature Administration is enabled,
changing to a provider locked for the organization is rejected with 403 forbidden —
but re-sending the agent’s current provider is always allowed, so a read-modify-write
round trip keeps working. cartesia can never be locked.For inworld, set voiceSettings.voice_id to a full Inworld voice string, e.g. Inworld.TTS2.Ashley.object
Voice configuration settings. Structure varies based on Cartesia voices also accept two optional fields:
English Cartesia voices accept a For MiniMax:Advanced Voice Timing (optional, applies to all TTS models):
Example with advanced timing:
ttsModel:For Cartesia:{"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02", "speed": 1.0}
| Property | Type | Description |
|---|---|---|
model | string | Cartesia TTS model — sonic-3 (the default) or sonic-3.6. The retired sonic-2 and sonic-3.5 are still accepted but are saved as sonic-3. On a Cartesia voice, must be in the sonic-3 family to use custom pronunciations (Inworld supports them on any model; MiniMax supports them on English agents only), and for any custom (cloned) voice_id. For pronunciations, set model explicitly: an omitted model is rejected even though it plays as sonic-3. |
language | string | Locale passed to Cartesia TTS (e.g. es). When set, must match the language registered for the selected voice_id. |
A custom (cloned)
voice_id runs on the sonic-3 family (sonic-3 or sonic-3.6). An omitted model counts as sonic-3, and a retired sonic-2 or sonic-3.5 is saved as sonic-3.model override but not a language override. Non-English voices (e.g. Spanish) require both:{
"voice_id": "d46e87a1-7c6d-4b18-9359-926f4a35ffdf",
"model": "sonic-3",
"language": "es",
"speed": 1.0
}
{"voice_id": "English_radiant_girl", "model": "speech-2.6-turbo", "speed": 1.0}
| Property | Type | Default | Description |
|---|---|---|---|
min_endpointing_delay | number | 0.1 | Seconds to wait after the caller stops speaking before the agent responds. |
max_endpointing_delay | number | 2.0 | Maximum seconds to wait when the caller pauses mid-sentence. |
vad_min_silence_duration | number | (system default) | Minimum silence duration in seconds for Voice Activity Detection. Omit to use the system default. |
ai_greeting_interruptible | boolean | false | Let the caller talk over the AI-personalized greeting (aiGreetingPrompt); the agent then responds to what they said. The static greeting is never interruptible. |
{
"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02",
"speed": 1.0,
"min_endpointing_delay": 0.1,
"max_endpointing_delay": 5.0,
"vad_min_silence_duration": 0.55
}
Call Features
boolean
Whether SIP call transfer is enabled. Supports:
- SIP extensions:
sip:extension@hostname - US phone numbers: Automatically formatted to
tel:+1XXXXXXXXXX
boolean
Whether the agent can send emails during or after calls
string
Instructions for the agent on how and when to use the email tool
boolean
Mobile calls detection: look up whether the caller’s number is a mobile phone before the call starts. This is not answering machine detection — see
amdConfig under Outbound Calling.boolean
Let a caller on the web-embedded agent be transferred. Stored as
false whenever transferEnabled is false. An update that turns transferEnabled on without sending this field turns web transfer on too; a GET-then-PUT that re-sends transferEnabled: true never overwrites a stored false.boolean
Whether to enable echo detection during calls
boolean
Enable call recording. Recordings are saved to cloud storage and accessible via the Call Logs API.
boolean
Include a link to the call recording in the post-call summary notification. This is a sub-option of call recording — it requires recording to be enabled (
enableRecording: true in this request, or already enabled on the agent). Sending true while recording is not enabled returns a 400. Note: the link is delivered via the call-summary email, so it is only actually sent when the agent also has an email recipient configured and a recording was produced; otherwise the flag is stored but has no effect.string
Rules and conditions for call transfers. Describes when and where the agent should transfer calls.Example:
"Transfer to +15551234567 if the caller asks for billing support. Transfer to +15559876543 for technical issues."array
Array of email addresses used for notifications and the email tool.Example:
["[email protected]", "[email protected]"]Note that GET /v1/agents/{id} returns this field as the stored string, not an array.string | null
UUID of the email server to use for this agent. Must belong to the same account. Pass
null to revert to the account’s default email server.Use the List Email Servers endpoint to retrieve available IDs.boolean
Whether this agent can ask a web-embedded caller to share their screen and reason about what it sees. See Screenshare. Ignored for SIP/PSTN calls, which have no browser to share.Only the off → on transition is gated: re-sending a stored
true is never blocked, and turning it off always succeeds. Turning it on returns 403 forbidden when:- Screenshare is locked for the organization via Feature Administration,
- the resulting model — this request’s
modelif given, otherwise the agent’s current one — cannot read images, or - the organization is flagged Healthcare, which can never opt in to Screenshare.
model change on this same request is refused with the same error if it would leave an agent whose screenshare is (or is being set) on using a model that can’t read images, unless this same request also sets screenshareEnabled: false.Localization
string
Agent language for speech recognition and responses.Supported values:
en-US- English (US)es-US- Spanish (US)multi- Multilingual (English, Spanish, French, German, Hindi, Russian, Portuguese, Japanese, Italian, Dutch)
string
Agent timezone in IANA format. Used for date/time operations and calendar scheduling.Example:
"America/New_York", "Europe/London", "Asia/Tokyo"Knowledge Base
array
Array of URLs the agent can reference for knowledge. The content at these URLs is ingested and made available to the agent during conversations.Example:
["https://example.com/faq", "https://example.com/pricing"]RAG Knowledge Base
boolean
Enable live vector search during calls. The account and the agent’s organization must both have Knowledge Base (RAG) enabled, otherwise the API returns
403 forbidden.string | null
Organization-level knowledge base ID for the agent to search. The knowledge base must belong to the same organization as the agent. Set to
null to remove the assignment.Use List Knowledge Bases to retrieve available IDs.object
RAG retrieval configuration. Unknown keys are rejected.
| Property | Type | Description |
|---|---|---|
top_k | number | Number of chunks to retrieve. Integer, 1–50. |
rewrite_query | boolean | Rewrite the caller’s utterance into a cleaner search query before retrieval. |
max_context_tokens | number | Maximum context tokens to pass into the agent. Integer, 1–200000. |
fallback_message | string | Optional fallback guidance when retrieval has no useful result. Up to 2000 characters. |
curl -X PUT https://api.telzino.com/v1/agents/123e4567-e89b-12d3-a456-426614174000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"ragEnabled": true,
"knowledgeBaseId": "11111111-1111-1111-1111-111111111111",
"ragConfig": {
"rewrite_query": true,
"top_k": 5
}
}'
Business (Google Place)
string | null
Google Place ID linking the agent to a Google Business Profile listing. The platform fetches the listing’s details (hours, location, phone, website, services, ratings) and the agent uses them as verified facts when answering callers.
- Set to a Place ID to link (or relink) the agent’s business.
- Set to
nullor""to unlink. - Changing or clearing the id also clears the cached listing data (
google_place_data,google_place_context,google_place_fetched_at), so details from the previously linked business are never served while the new listing is fetched. Re-sending the already-stored id is a no-op and keeps the cache.
"ChIJVS6XxJPFU4gRoE8c8ql2lCQ"Warm Transfer
boolean
Enable warm transfer routing for this agent.
string
What the AI says to the human agent on the consult leg of a warm transfer. Blank uses the built-in briefing. See Warm Transfer.
string
Plain-language rules for when and where to warm transfer. Example:
"If the caller asks for billing, confirm first and warm transfer to pbx:1234."string
Public URL of the hold-music file played during warm transfer. The easiest way to set this is Upload Hold Music, which saves the URL for you. Leave empty for built-in hold music.
boolean
Whether the original caller’s number is passed to the transfer destination. Defaults to
true.Pronunciations
array
Custom pronunciation rules for specific words and names in IPA format. Replaces the entire pronunciations list for the agent.Each entry is an object with:
Custom pronunciations are supported on Inworld voices, Cartesia voices using the
sonic-3 or sonic-3.6 model, and MiniMax voices on English agents only. They are not supported on Cartesia sonic-2, nor on MiniMax when the effective language is es-US or fr-FR — MiniMax accepts pronunciation phonemes for English only and mis-renders Spanish and French. A non-empty pronunciations array is rejected with 400 unless the agent’s effective voice is ttsModel: minimax, ttsModel: inworld, or ttsModel: cartesia with a sonic-3-family voiceSettings.model (taking either the values in this request or the agent’s existing settings; an unset model does not count). A retired sonic-2 sent in this request is saved as sonic-3 first and is accepted; a sonic-2 already stored on the agent is not. Send an empty array [] to clear rules on any voice.You store each pronunciation once and it works on any supported voice, provided it was generated with Apply — the phonemes must be IPA in Cartesia’s <<…>> form. Hand-typed free-text phonemes work on Cartesia only and are silently ignored on MiniMax and Inworld, and rules generated for a Spanish or French voice are ignored on MiniMax. Multi-word entries such as New York work on Cartesia but are ignored on MiniMax and Inworld, whose syntax covers a single word each.Each entry also accepts an optional lang (en | es | fr) recording which language the phonemes were written for. It decides whether MiniMax can speak the row — MiniMax accepts English phonemes only. Omit it and the agent’s language is used as the fallback.word— The word or name (1–100 characters)phonetic(optional) — A plain-English respelling of how the word should sound (e.g.,jay-kob-sen). Used by the dashboard’s “Apply” helper to generate the IPAphonemes; not used directly by the agent.phonemes— The IPA phoneme stringlang(optional) —en|es|fr. Which language the phonemes were written for, recorded when they are generated. Decides whether MiniMax can speak the row (MiniMax accepts English phonemes only); omit it and the agent’slanguageis used as the fallback.
[] to clear all pronunciation rules.Example:[
{ "word": "Telzino", "phonemes": "<<t|ɛ|l|ˈz|iː|n|oʊ>>" },
{ "word": "Jacobsen", "phonetic": "jay-kob-sen", "phonemes": "<<ˈ|dʒ|eɪ|k|ə|b|s|ə|n>>" }
]
Advanced Configuration
Outbound Calling
These fields apply to agents that place calls. See Outbound Calls. Each object is sent whole and replaces the stored value; omit a field to leave it unchanged.contactPolicy is the exception: its keys merge into the stored policy, and contactPolicy: null clears it.
string
A fixed line spoken word for word before the outbound greeting — for warnings or advisories that the AI must never rephrase. Leave it empty and the agent starts from its greeting or instructions.
boolean | object
Answering Machine Detection.
Unknown keys are rejected.
true enables it with the defaults, false disables it, an object tunes it. Unrelated to callDetection.| Key | Values | Default |
|---|---|---|
enabled | boolean | false |
sensitivity | fast, balanced, precise | balanced |
max_detection_ms | integer, null = the preset’s budget | null |
on_machine | leave_voicemail, hang_up, continue_as_human | leave_voicemail |
on_silence | reprompt, hang_up, continue_as_human | reprompt |
uncertain_prompt | string, max 500 | ”Hello, is this a good time?” |
voicemail_max_duration_ms | integer | 25000 |
voicemail_beep_wait_ms | integer | 2000 |
GET /v1/agents/{id} returns this as amd_config — rename it to amdConfig before sending it back.string
Spoken after the beep when detection reaches a voicemail box and
on_machine is leave_voicemail. Empty means hang up without leaving a message.string
Pre-recorded voicemail audio, played instead of speaking
voicemailMessage when set. If it cannot be fetched on the call, the text is spoken instead.boolean | object
IVR Menu Navigation.
true enables it with the defaults, an object tunes it: enabled, target_intent (reach_human, reach_department, custom), target_department (max 200), spoken_keywords (up to 20 strings), max_attempts (1–10), max_duration_ms (up to 600000), fallback_action (press_zero, hang_up, wait_for_human), hold_max_wait_ms (up to 1800000).Requires detection: a request that would leave navigation on while amdConfig is off is refused with 400, in either direction — send both fields to change them together. dtmf_transport is not accepted (the DTMF method is a trunk-level setting). Returned by GET as ivr_navigation.string | null
The retry policy every call this agent places follows, unless the trigger request sends its own
retry_policy_id. Must belong to the account.object | null
Compliance controls for this agent.
null or omitted means none: nothing is required and nothing is refused. Unknown keys are rejected.require_consent_basis(boolean) — every trigger must send aconsent_basisor is refused with400 consent_requiredcalling_windows(object) —start_localandend_localas 24-hourHH:MMon the callee’s clock (end exclusive, no wrapping midnight),timezone_source(callee_npanxx,agent_timezone,explicit) andtimezone(required forexplicit)abandon_rate_alert_threshold(number 0–1) — the abandon rate above whichGET /v1/metricsflags this agent. Defaults to0.03
contact_policy, which holds per-number attempt caps. Returned by GET as contact_policy.object
General configuration object.Common properties:
| Property | Type | Description |
|---|---|---|
enable_recording | boolean | Enable call recording |
user_timezone | string | IANA timezone (e.g., America/New_York) |
inactivity_timeout | number | Seconds before inactivity prompt |
ambient_type | string | Type of ambient background audio |
ambient_volume | number | Volume level for ambient audio |
thinking_sounds_enabled | boolean | Enable thinking/processing sounds during pauses |
thinking_volume | number | Volume level for thinking sounds |
thinking_clip_id | string | Sound clip to play while the agent is thinking. KEYBOARD_TYPING (default) or KEYBOARD_TYPING2 |
background_audio_enabled | boolean | Enable background audio during calls |
confirm_transfers | boolean | When true, the agent confirms a transfer is answered before connecting the caller — via a press-1 keypress, or via BLF presence for registered SIP extensions. Defaults to false. Only applies when call transfer is enabled. |
transfer_announcement_enabled | boolean | Set to false to silence the line the platform speaks right before a transfer (“I am now transferring your call. Please stay on the line while I connect you.”). Defaults to true. When off the platform says nothing: a prompt instruction telling the agent to announce the transfer is not reliably followed, so some callers hear silence. Prefer transfer_announcement when you want your own wording spoken on every call. |
transfer_announcement | string | Replaces the platform’s spoken transfer line with your own short sentence (max 97 characters), e.g. "Of course, transferring you to our customer service team now.". The platform speaks it on every transfer. Blank or omitted keeps the default line. |
summary_context | string | Additional instructions for the AI-generated call summary. Use this to tell the summary what to focus on or what data to extract (e.g., "Focus on customer complaints. Extract any mentioned order numbers."). |
object
Extensions configuration for webhooks and MCP servers.Webhook structure:MCP server objects (All
Adding or changing the source means adding the tool, changing its canonical
Because
{
"tools": [
{
"type": "webhook",
"name": "Log_Call_To_CRM",
"friendlyName": "Log to CRM",
"description": "Logs call details and transcript to the CRM after the call ends",
"condition": "after_call",
"method": "POST",
"url": "https://api.yourcrm.com/calls/log",
"headers": [
{ "key": "Authorization", "value": "Bearer YOUR_API_TOKEN" }
],
"parameters": [
{ "key": "caller_phone", "value": "" },
{ "key": "call_summary", "value": "" },
{ "key": "transcript", "value": "" },
{ "key": "sentiment", "value": "" }
]
}
],
"mcp_servers": [
{
"url": "https://mcp.acme.com/mcp",
"name": "Acme CRM MCP"
}
]
}
mcp_servers):Same request validation as Create Agent — MCP request body: name and url are both required on each entry; transport_type, auth_headers (array of {key, value}), initialization_options (object), and allowed_tool_names (array of tool names the agent may use from that server; omitted or empty means every tool) are optional. Because extensions is replaced in full, an update that sends an MCP entry without allowed_tool_names removes that connection’s tool limit — resend the list to keep it. (Exception: on an OpenTable reservation agent’s StayAPI connection, leaving it out gives the 8 OpenTable tools, never StayAPI’s other tools.) Names must match the server’s tool names exactly. When the server can be reached, names it doesn’t publish are removed and reported in warnings, and a list in which no name matches is rejected with 400. pre_answer_calls (tools the platform calls while the phone is ringing — see Pre-Answer) is also optional and follows the same full-replace rule: an MCP entry sent without it has no pre-answer tools, so resend the list to keep them. The platform transforms each entry on write (name → connection_name, url → mcp_endpoint_url) so the stored shape differs from the request shape — see Get Agent — Stored MCP Connection Shape.Webhook conditions:before_call- Execute before the agent answersduring_call- Execute while call is activeafter_call- Execute after call ends
type: "listings_search"). Lets the agent search a listings page during a call; see Listings Search. An agent can have one.{
"type": "listings_search",
"name": "search_listings",
"friendlyName": "Listings_Search",
"description": "When a caller asks what rentals are available, search listings as soon as they give a location, budget or bedroom count, and read two or three that match.",
"condition": "during_call",
"parameters": [
{ "key": "listings_url", "value": "https://yourcompany.appfolio.com/listings" },
{ "key": "attested_by", "value": "[email protected]" },
{ "key": "business_name", "value": "Your Company" }
]
}
parameters values are strings. Keys you leave out take their defaults. The stored tool always carries all ten keys, and name, friendlyName and condition are always stored as shown above, so a GET returns the tool in this full form.| Key | Required | Meaning |
|---|---|---|
listings_url | yes | Your listings page, https:// only. Stored in canonical form: an AppFolio page becomes https://<company>.appfolio.com/listings; any other page keeps its path and query. The address rules and messages are listed on Listings Search. |
attested_by | when adding or changing the source | The email address of the person attesting that the business owns, or is authorized to display, the page. Recorded as declared by the API caller. |
attested_at | no | Always set by the server to the time of the write that added or changed the source. A value you send is ignored. |
adapter | no | Derived from listings_url: appfolio or generic. A value that contradicts the address is rejected. |
profile | no | rentals (the only value). |
refresh_minutes | no | 10 (default), 15, 30 or 60. |
fetch_detail_pages | no | "true" (default for AppFolio) reads each listing’s own page for deposit and application fee. Always stored "false" for a non-AppFolio page. |
business_name | no | The name listing texts are “sent by”, at most 100 characters. Empty (default) uses the agent’s name. |
sms_provider | no | twilio, netsapiens, or empty (default: whichever carrier is connected to the agent). |
sms_sender_extension | no | 2 to 10 digits, or empty (default: the agent’s own extension). |
listings_url, or turning fetch_detail_pages from "false" to "true" on an AppFolio page. Only that needs attested_by, sets a new attested_at, and is checked against the Listings Search gate (403). Any public https:// listings page can be saved this way once attested; a non-AppFolio page needs no extra step. Re-sending the tool unchanged (a different URL casing, a trailing / or a query string on an AppFolio address counts as unchanged) keeps the stored attested_by and attested_at whatever you send, but the tool is still validated against the rules below.enabled: false is refused: to switch Listings Search off, remove the tool. Another tool in extensions.tools whose function name is search_listings or send_listing_link is refused too, because Listings Search registers those names.An invalid tool is rejected with 400 Invalid request data, and details names the problem:details[].message | Cause |
|---|---|
Adding or changing a listings source requires the attestation: attested_by (the email address of the person attesting). | attested_by is missing or not an email address on an add or change. |
An agent can have only one Listings Search function. | More than one listings_search tool. |
Another function is named "search_listings", which Listings Search needs. Rename or remove that function to use Listings Search. (or "send_listing_link") | Another tool registers one of the Listings Search function names. |
Listings Search can't be switched off with enabled: false. Remove the Listings Search function instead. | The tool was sent with enabled: false. |
adapter must be "appfolio" for this listings_url (it is derived from the address). (or "generic") | adapter contradicts the address. |
profile must be "rentals"., refresh_minutes must be 10, 15, 30 or 60., fetch_detail_pages must be "true" or "false"., sms_provider must be "twilio", "netsapiens" or empty., sms_sender_extension must be 2 to 10 digits., business_name must be 100 characters or fewer. | That parameter has an invalid value. |
Listings Search instructions must be 4,000 characters or fewer. | description is too long. |
An address message, e.g. The listings address must start with https:// | listings_url is refused; the messages are listed on Listings Search. |
extensions is replaced as a whole, leaving the listings_search tool out of extensions.tools removes it from the agent. To keep it, send it back as GET returned it.Integrations
object
Third-party integration configurations. Supported provider keys: Provider fields (required when creating a new provider entry; partial updates only need to leave the stored object valid):
Behavior notes:
modmed, googleCalendar, outlookCalendar, twilio, netsapiens. Unknown provider keys are stored as-is.Update semantics — unlike extensions (which is a full replace), integrations uses a field-level merge per provider:- Only the fields you send change; omitted fields and omitted providers are preserved.
- Set a field to
nullto clear that field. - Set a whole provider to
nullto remove the provider.
fromNumber and removes the ModMed integration — everything else is untouched:{
"integrations": {
"twilio": { "fromNumber": "+15551234567" },
"modmed": null
}
}
| Provider | Required fields | Optional fields |
|---|---|---|
twilio | — (credentials cannot be set or changed here; see below) | fromNumber |
modmed | firm_url_prefix, username, password, environment (sandbox, practice_sandbox, production) | tokens, connected_at |
googleCalendar | access_token, expires_at | refresh_token, email, selected_calendar_id, selected_calendar_name, timezone |
outlookCalendar | access_token, expires_at | refresh_token, email, selected_calendar_id, selected_calendar_name, timezone |
netsapiens | url, username, password, domain, client_id, client_secret, extension, access_token | api_version (v1, v2), refresh_token, user_scope, connected_at |
- Configs are stored as-is — credentials are not validated against the provider. ModMed/NetSapiens entries written without valid tokens will not function at runtime until tokens are supplied or the integration is connected via the Dashboard.
- Writing
twiliohere does not add thesend_sms_via_twiliotool toextensions(Connect does when an agent’s From Number is saved; the Dashboard does not) — add the tool yourself if you want the agent to send SMS. - Twilio credentials are not set on an agent. An organization has one Twilio connection, set in the Dashboard or Connect, and every agent in the organization uses it. A request whose
twilioblock sets or changesaccountSid,authToken,apiKeySidorapiKeySecretis rejected with400. Sending back the values a GET returned is not a change, so a GET → modify → PUT round-trip still succeeds. The same rule applies to aTwilio(capital T) block. Sendingnullfor a credential field that is stored counts as a change and is rejected; to remove the agent’s whole Twilio block, includingfromNumber, send"twilio": null. If the agent’s own credentials were removed between your GET and your PUT, they are gone, so echoing them is a change and is rejected — GET again first.fromNumber(the number this agent texts from when it has no Send SMS function with its own From Number) is still accepted. - Secret fields (passwords,
client_secret, access/refresh tokens) are write-only: they are accepted on PUT but never returned by any agent endpoint. The merge semantics make this safe — a GET → modify → PUT round-trip won’t wipe stored secrets.
400 — Twilio credentials sent on an agent
{
"error": "Twilio credentials can no longer be set on an agent. Connect Twilio once for the organization (open any of its agents → Integrations → Twilio in the Dashboard or Connect); every agent in the organization uses that connection. integrations.twilio accepts only fromNumber."
}
MCP can be set in the Dashboard or via
extensions.mcp_servers here (see Create Agent — MCP request body); persisted shape on read is documented under Get Agent.MCP servers are auto-tested on save. Each entry in
extensions.mcp_servers is tested when you update the agent: reachable servers are stored with a fingerprint and discovered tools (green Connected badge in the Portal); unreachable servers are still stored but without a fingerprint (yellow Not tested badge), and the response includes a non-fatal warnings array ([{ "connection_name": "...", "message": "..." }]). Re-testing on update means an MCP server you keep in the payload retains its verified status instead of being reset.To validate a configuration before assigning it, use Test MCP Connection.curl -X PUT https://api.telzino.com/v1/agents/123e4567-e89b-12d3-a456-426614174000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Agent Name",
"systemPrompt": "Updated system prompt with new instructions...",
"status": "active",
"ttsModel": "cartesia",
"voiceSettings": {"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02", "speed": 1.1},
"transferEnabled": true,
"transferRules": "Transfer to +15559876543 for technical issues",
"language": "multi",
"timezone": "Europe/London",
"knowledgebaseLinks": ["https://example.com/updated-faq"]
}'
const agentId = '123e4567-e89b-12d3-a456-426614174000';
const response = await fetch(`https://api.telzino.com/v1/agents/${agentId}`, {
method: 'PUT',
headers: {
'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'Updated Agent Name',
systemPrompt: 'Updated system prompt...',
status: 'active',
transferEnabled: true,
language: 'multi',
timezone: 'Europe/London',
knowledgebaseLinks: ['https://example.com/updated-faq']
})
});
const { data } = await response.json();
import requests
agent_id = '123e4567-e89b-12d3-a456-426614174000'
response = requests.put(
f'https://api.telzino.com/v1/agents/{agent_id}',
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN'},
json={
'name': 'Updated Agent Name',
'systemPrompt': 'Updated system prompt...',
'status': 'active',
'transferEnabled': True,
'language': 'multi',
'timezone': 'Europe/London',
'knowledgebaseLinks': ['https://example.com/updated-faq']
}
)
agent = response.json()['data']
curl -X PUT https://api.telzino.com/v1/agents/123e4567-e89b-12d3-a456-426614174000 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"extensions": {
"tools": [
{
"type": "webhook",
"name": "Create_Ticket",
"friendlyName": "Create Support Ticket",
"description": "Creates a support ticket in Freshdesk after every call",
"condition": "after_call",
"method": "POST",
"url": "https://yourcompany.freshdesk.com/api/v2/tickets",
"headers": [
{ "key": "Authorization", "value": "Basic YOUR_FRESHDESK_API_KEY" },
{ "key": "Content-Type", "value": "application/json" }
],
"parameters": [
{ "key": "subject", "value": "" },
{ "key": "description", "value": "" },
{ "key": "customer_email", "value": "" },
{ "key": "priority", "value": "2" }
]
}
],
"mcp_servers": [
{
"name": "Acme CRM",
"url": "https://mcp.acmecrm.com/sse",
"transport_type": "sse",
"auth_headers": [
{ "key": "Authorization", "value": "Bearer YOUR_CRM_TOKEN" }
]
}
]
}
}'
Secrets are never returned. Responses from this and every other agent endpoint omit secret fields: integration passwords,
twilio.authToken and twilio.apiKeySecret (on agents that still carry their own Twilio credentials), netsapiens.client_secret, calendar access/refresh tokens, and the SIP password inside sip_registration_data. Non-secret metadata (provider keys, usernames, accountSid, connected_at, calendar names) is still returned so you can see what’s configured.{
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"organization_id": "987fcdeb-51a2-3bc4-d567-890123456789",
"name": "Updated Agent Name",
"description": "Handles customer inquiries",
"greeting_message": "Hello! How can I help you today?",
"system_prompt": "Updated system prompt...",
"agent_type": "simple",
"model": "gpt-4.1",
"voice_model": "gpt-4o-realtime-preview",
"stt_model": "deepgram",
"tts_model": "cartesia",
"voice_settings": {"voice_id": "f786b574-daa5-4673-aa0c-cbe3e8534c02", "speed": 1.1},
"config": {},
"extensions": {},
"integrations": {},
"transfer_enabled": true,
"transfer_rules": "Transfer to +15559876543 for technical issues",
"language": "multi",
"timezone": "Europe/London",
"enable_recording": false,
"echo_detection": false,
"call_detection": false,
"knowledgebase_links": ["https://example.com/updated-faq"],
"email": null,
"email_tool_enabled": false,
"email_server_id": null,
"pronunciations": [],
"status": "active",
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-16T14:20:00Z"
}
}
{
"error": "Invalid request data",
"details": [
{
"code": "invalid_enum_value",
"message": "Invalid enum value. Expected 'simple' | 'advanced' | 'custom' | 'appointment_scheduler' | 'receptionist' | 'message_taker' | 'blank_slate' | 'outbound', received 'invalid'",
"path": ["agentType"]
}
]
}
{
"error": "forbidden",
"error_description": "Access denied"
}
{
"error": "forbidden",
"error_description": "Model not available for this organization: claude-sonnet-4-6"
}
{
"error": "forbidden",
"error_description": "TTS provider not available for this organization: minimax"
}
{
"error": "forbidden",
"error_description": "Feature(s) not available for this organization: listings_search"
}
{
"error": "forbidden",
"error_description": "Screenshare requires a model that can read images. Choose a different model, or turn screenshare off."
}
{
"error": "forbidden",
"error_description": "Screenshare is not available for healthcare organizations."
}
{
"error": "not_found",
"error_description": "Agent not found or access denied"
}
Update Behavior
- Only provided fields are updated; omitted fields retain their current values
- The
updated_attimestamp is automatically set to the current time - The
providerfield cannot be changed after creation
Feature Administration gating
When Feature Administration is enabled, updates are validated against the feature gates of the agent’s organization — including locks inherited from its reseller or partner. The API returns403 forbidden when the request:
-
configures an integration under
integrations(e.g.googleCalendar,twilio) that is locked for the organization — clearing one (setting it tonull) is always allowed; -
sets
ragEnabled: truewhile Knowledge Base (RAG) is locked; -
sets
warmTransferEnabled: truewhile Warm Transfer is locked; -
newly selects a cloned voice in
voiceSettings.voice_idwhile Custom Voices is locked — an agent already using a clone keeps it, and you can still switch away from it; -
sets a different
googlePlaceIdwhile Business Listing (Google Places) is locked. Re-sending the agent’s current Place ID is not blocked, so reading an agent and writing the whole object back keeps working for a locked organization; -
changes
agentTypetoreceptionist,appointment_scheduler,message_taker,blank_slate,outbound,opentable_reservationortranslatorwhile the matching Template is locked. Re-sending the agent’s currentagentTypeis not blocked, so a read-modify-write round trip still works. Values ofsimple,advancedandcustomare never gated —simpleis the stored type for the Support Agent (CRM) templates and the schema default, so it cannot be attributed to a single template; -
changes
ttsModelto a text-to-speech provider that is locked for the organization. Re-sending the agent’s current provider is never blocked, so a read-modify-write round trip keeps working, and switching away from a locked provider always succeeds onto an available one.cartesiais never gated — it cannot be locked, so an agent can always be moved to a working voice; -
arms
screenshareEnabled(absent/false→true) while Screenshare is locked for the organization. Only the arming transition is gated: re-sending a storedtrueis never blocked, and turning it off always succeeds; -
adds the
ns_reset_passwordtool toextensions.toolswhile NS Password Reset is locked. Only the addition is gated: re-saving an agent that already has the tool is not blocked, and removing it always succeeds; -
adds a
listings_searchtool toextensions.tools, or changes itslistings_url, or switches itsfetch_detail_pagesfrom"false"to"true", while Listings Search is locked. The error isFeature(s) not available for this organization: listings_search, and it is returned even when the tool is also invalid. Re-sending the tool unchanged is never blocked by the lock, and removing it always succeeds; -
adds a UCaaS function to
extensions.toolswhile that function — or the Netsapiens integration itself — is locked. Each function has its own gate:A Chat Notification tool is recognised by more than its name: any tool withTool name in extensions.toolsFeature that must be available ns_get_timeframeNS Get Timeframe ns_verify_userNS Verify User ns_lookup_directoryNS Lookup Directory ns_set_call_forwarding,ns_read_call_forwarding,ns_delete_call_forwardingNS Set Call Forwarding ns_send_smsNS Send SMS ns_check_sms_repliesNS Check SMS Replies NS_Chat_Messaging,ns_send_chat,ns_check_chat_messagesNS Chat Notification type: "notification"counts if its name beginsns_chat/NS_Chat, if it carries a{"key": "type", "value": "ns_chat"}parameter, or if itsnotificationparameter contains"type": "ns_chat". Those are the same shapes the agent runtime accepts, so a payload that would start a chat is gated whatever it is called. Only the addition is gated: re-sending tools the agent already has is not blocked, and removing one always succeeds — a lock can never strand a UCaaS function on an agent. Netsapiens is checked as well as the function’s own feature, so withdrawing the integration blocks all seven at once; when both are locked the error names the integration.You can build that request from aGETresponse:tools[].typeaccepts the internal types aGETreturns, including thenetsapienstype these UCaaS tools carry, so an agent’s existingextensions.toolscan be sent back. Two things to know:- Tool
parametersmust be an array of{"key": ..., "value": ...}strings. The other shapes the Dashboard templates store — the Zendesk template’s parameters object, and the Freshdesk template’s numeric values with atypekey — are accepted only when that whole tool is sent back exactly asGETreturned it, so those tools cannot be edited through this endpoint. Parameters in any other shape are rejected with400, including{"name": ..., "value": ...}items and flow-builder custom function tools. - A
paymenttool is rejected with400. Becauseextensionsis replaced as a whole, removing thepaymenttool fromtoolsturns card payments off. Re-enable them afterwards withPUT /v1/agents/{agentId}/payments/tool, re-sendingsuccess_url,max_single_charge_cents,max_per_call_centsandmax_attempts_per_call— read them first fromGET /v1/agents/{agentId}/payments/readiness— or they reset to their defaults.
- Tool
googlePlaceId: null, ragEnabled: false, warmTransferEnabled: false, screenshareEnabled: false or integrations.<name>: null always succeeds — so a lock can never leave an agent with configuration you cannot remove, nor stop you editing an agent’s other fields. Agents created before a template was locked stay fully editable for the same reason.
The Screenshare model check is independent of Feature Administration, and can also fire on a plain model change. Arming
screenshareEnabled — or simply changing model on an agent whose screenshare is already on — returns 403 forbidden when the resulting model cannot read images, whether or not Feature Administration is enabled or Screenshare itself is locked. The one exception: a request that changes model and also sets screenshareEnabled: false in the same call is never blocked on this ground, since screenshare is being turned off at the same time. See screenshareEnabled above.The Screenshare Healthcare refusal is independent of Feature Administration too. A Healthcare-flagged organization can never opt in to Screenshare, so arming an agent there returns
403 forbidden whether or not Feature Administration is enabled and whether or not Screenshare itself is locked. Turning screenshare off is still never blocked./v1/knowledge-bases endpoints apply the same gate and return 403 forbidden when Knowledge Base is locked for the organization. POST /v1/custom-voices returns 403 when Custom Voices is locked, and GET /v1/places/search returns 403 when Business Listing is locked for the account.