On this page
Agents
An agent is a flow graph plus its settings. You change it through its draft, check it, and publish immutable versions; runs execute a version, never the draft.
Create an agent
Start from a template, clone an existing agent, or import a Retell export. Each needs an Idempotency-Key.
| Template | Channel | Starts as |
|---|---|---|
| blank | cloud or phone | One conversation node with an empty prompt. |
| cloud_task | cloud | A task: takes the run input, works through it, ends. Task mode. |
| cloud_chat | cloud | A conversation over messages you send to the run. Chat mode. |
| phone_outbound | phone | An outbound call: greet, say who it is calling for, ask how to help. Records both channels. |
POST /api/v1/agents takes name, template, channels and languages(en, es). A template must match a channel you choose. POST /api/v1/agents/{agent_id}/clone copies an agent's head, a version you name, or its draft if it was never published, into a new agent's first draft.
Import from Retell
POST /api/v1/agents/import turns a Retell conversation-flow agent export into a phone agent's draft. An import never goes live: test the draft, then publish it. A reference.id no agent has creates a new agent; the ID of an existing agent replaces its draft and leaves its live version running. Split the export: its conversationFlow is flow, the rest is agent; agent_version is Retell's version, kept as provenance. Retell LLM (single-prompt) agents are not accepted. Check an export first with POST /api/v1/agents/import/inspect: it compiles the same body, returns the diagnostics, and says what the import would do in target (a new agent, a new or replaced draft, and whether that draft has unpublished changes). It writes nothing.
POST /api/v1/agents/import
{
"reference": { "tenant_id": "<your environment's tenant_id>", "kind": "agent", "id": "fairway-rpc-outbound" },
"agent_version": 6,
"agent": { …the Retell agent object without conversationFlow, "version": 6, "webhook_url": null… },
"flow": { …the export's conversationFlow: conversation_flow_id, version, nodes, tools… },
"config": {
"providers": {
"llm": { "provider": "openai", "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } },
"stt": { "provider": "elevenlabs", "model": "scribe_v2_realtime" },
"tts": { "provider": "elevenlabs", "model": "eleven_flash_v2_5", "voiceId": "<a voice_id from GET /api/v1/voices>" }
},
"recording": { "required": true, "channels": "dual" }
},
"model_bindings": {
"$.flow.model_choice": { "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } },
"$.agent.post_call_analysis_model": { "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } }
}
}
reference.tenant_idis your environment's:GET /api/v1/environmentslists production and sandbox with theirtenant_idand which one your key is in (current).config.providerschooses the models the agent runs on: areadychat model fromGET /api/v1/modelsforllm(withprovider: "openai", the platform route for every catalog model), and a speech model and voice fromGET /api/v1/voices. The Retell voice is replaced by that voice (warningvoice_rebound).model_bindingsmaps each model the export declares to a catalog model. Run inspect with an empty object first: everymodel_binding_requireddiagnostic names one path to bind (usually$.flow.model_choiceand$.agent.post_call_analysis_model).- Set
agent.webhook_urltonull, or pin the same URL asconfig.webhook({ url, secretRef: "<secret id>@<version>" }, awebhook_signingsecret); a different URL is refused withwebhook_mismatch. config.telephony({ trunkId, from }) is optional. Leave it out until your trunk and numbers exist, then add them with theagent.telephonyedit (Phone).- Retell tools become HTTP actions. Inline secrets in tool headers or query values, and custom SIP headers, are refused (
private_inline_value): move them to a secret. - Locales other than English and Spanish are dropped with a warning.
- Replacing a draft with unpublished changes needs
replace_draftwith that draft'sdraft_idand revision (expected_revision); without it the import is refused with409 conflict, reasondraft_has_changes, so no client discards work by accident. - Importing the same body onto its unedited draft again returns that draft (200) and writes nothing.
| Operation | MCP tool | Key role | Purpose |
|---|---|---|---|
GET /api/v1/agents | agents_list | viewer | List agents with their head publication, open draft and last run; search, filter and order server-side |
GET /api/v1/agents/{agent_id} | agents_get | viewer | Get one agent |
POST /api/v1/agents | agents_create | member | Create an agent from a template (201; an idempotent replay returns 200) |
DELETE /api/v1/agents/{agent_id} | agents_delete | member | Delete (archive) an agent: it leaves the catalog and takes no new drafts, publications or runs; history is kept |
POST /api/v1/agents/{agent_id}/clone | agents_clone | member | Clone an agent's head, chosen version or never-published draft into a new agent's first draft (201; replay 200) |
POST /api/v1/agents/import | agents_import | member | Import a Retell agent export as a draft, never live: a new agent gets it as its first draft; an existing agent's draft is replaced and its head is unchanged. Replacing a draft with unpublished changes needs replace_draft {draft_id, expected_revision} naming it (409 reason draft_has_changes otherwise). Publish the draft with publications.publish (201; re-importing the same export onto its unedited draft returns 200) |
POST /api/v1/agents/import/inspect | agents_import_inspect | member | Compile a Retell export and say what agents.import would do (new agent, new or replaced draft, unpublished changes it would discard) without importing it |
The flow graph
A flow has a global prompt, a start node and nodes joined by transitions. Each node has an instruction (a prompt, or static text to say) and edges whose conditions are prompts the model evaluates, equations on variables (==, contains, is_set, …) or always. Node kinds:
conversation``end``branch``transfer_call``function``extract_dynamic_variables``component
functionnodes call a tool (an HTTP action you define), optionally waiting for its result.extract_dynamic_variablesnodes pull typed values out of the conversation into variables.transfer_callnodes transfer a phone call, cold or warm, to a number or a{{variable}}.componentnodes run a reusable sub-flow;endends the call or conversation.- Prompts use
{{variables}}: a run'sdynamic_variablesfill them, and a phone run missing one the agent needs is refused (Dynamic variables).
Talos's canvas edits the same graph; its node positions are presentation only and never change a version's digest.
Drafts: edit and validate
An agent has at most one draft. POST …/draft opens one from the head (or from base_version). Every PATCH …/draft sends up to 100 typed edits with the draft_id and the expected_revision it was based on; if someone else edited first you get 409 conflict and reload. There are no free-form paths: each change is one of these edits.
Edit a draft
PATCH /api/v1/agents/{agent_id}/draft
{
"draft_id": "5b0e…",
"expected_revision": 4,
"edits": [
{ "type": "flow.prompt", "text": "You are Maya, calling on behalf of Acme Dental about {{appointment_time}}." },
{ "type": "languages.set", "languages": ["en", "es"] },
{ "type": "agent.telephony", "value": { "trunkId": "my-twilio-trunk", "from": "+14155550100" } }
]
}
agent.name``agent.settings``agent.analysis``agent.provider``agent.telephony``agent.recording``flow.prompt``node.name``node.instruction``node.transitions``node.configure``node.create``node.remove``flow.configure``component.create``component.start``component.remove``transfer.credential``channels.set``languages.set``trigger.put``trigger.remove``action.put``action.remove``notification.put``notification.remove``knowledge.set``cloud.io.set``layout.set
POST …/draft/validate compiles the draft and returns { ok, version, digest, diagnostics }: the version and digest it would publish, and every warning or error. An error blocks publishing. A finding that has a mechanical fix lists it in fixes as { id, label, edits }: send edits as a PATCH …/draft with the revision you validated, then validate again. A draft that changed since refuses the fix with 409 conflict.
| Operation | MCP tool | Key role | Purpose |
|---|---|---|---|
GET /api/v1/agents/{agent_id}/draft | drafts_get | viewer | Read the open draft |
POST /api/v1/agents/{agent_id}/draft | drafts_open | member | Open a draft from the head or a chosen version |
PATCH /api/v1/agents/{agent_id}/draft | drafts_edit | member | Apply domain edits to the draft (revision compare-and-set) |
DELETE /api/v1/agents/{agent_id}/draft | drafts_discard | member | Discard the draft (revision compare-and-set) |
POST /api/v1/agents/{agent_id}/draft/rebase | drafts_rebase | member | Rebase the draft onto the live version after a head change (keeps its definition; 409 head_moved when live moved again) |
POST /api/v1/agents/{agent_id}/draft/validate | drafts_validate | member | Compile the draft and return diagnostics and the would-be digest |
Publish and roll back
POST …/publications with the draft_id, expected_revision and the next version (0 for the first) compiles the draft into an immutable publication. By default it becomes the head, and runs that name no version use it from then on. Options:
"live": falsepublishes a test version: you can run it by version, but the head does not move and its triggers are not registered. Test-publishing a draft revision that already has a test version returns that version instead of a new one, so run the version in the response."keep_draft": truekeeps the draft open for more changes.
To go live safely: validate, publish with "live": false, run your test suites against that version, then promote it.
POST …/head moves the head to any existing version: promote a test version, or roll back. It takes expected_head_version, the head you saw (null for none), and refuses the move with 409 conflict if the head has moved since. Runs already admitted keep the version they started with.
| Operation | MCP tool | Key role | Purpose |
|---|---|---|---|
POST /api/v1/agents/{agent_id}/publications | publications_publish | member | Publish the draft as the next immutable version; moves head unless live is false (201; replay 200) |
GET /api/v1/agents/{agent_id}/publications | publications_list | viewer | List publications of an agent |
GET /api/v1/agents/{agent_id}/publications/{version} | publications_get | viewer | Get one publication with its compiled plan |
POST /api/v1/agents/{agent_id}/head | publications_promote | member | Move head to an existing version (manual promote or rollback), rebasing the named open draft in the same transaction |
Actions and governed effects
Everything an agent does outside the conversation is an action: calling your HTTP endpoint, transferring or ending a call, leaving a voicemail, scheduling a callback, searching knowledge, delivering a webhook. Each time a run takes one, Talos checks it against the run's live grant just before sending, records the decision, and settles it as an effect with a receipt (intended → sending → succeeded | failed | uncertain). A denied action sends nothing. An unacknowledged one is uncertain and is never blindly resent. Read them with GET /api/v1/runs/{run_id}/effects.
| Built-in action | What it does |
|---|---|
| webhook.deliver | Deliver run webhook |
| http.request | Customer HTTP action |
| call.transfer | Transfer call |
| run.end | End call or conversation |
| call.voicemail | Handle voicemail |
| callback.schedule | Schedule callback |
| knowledge.search | Search knowledge |
| call.dial | Dial outbound call |
| livekit.room | Create media room |
| livekit.dispatch | Dispatch voice agent |
| run.analyze | Analyze run |
Your own HTTP actions (action.put) call HTTPS endpoints with none, bearer, basic or header authentication, the credential held as a secret. Its response_variables set flow variables from the action's 2xx JSON response, each read at a path:{"balance": "$.account.balance"} makes the nested field {{balance}}.
| Operation | MCP tool | Key role | Purpose |
|---|---|---|---|
GET /api/v1/actions | actions_list | viewer | List the action descriptors available to publications |
GET /api/v1/voices | voices_list | viewer | List the speech models and platform TTS voices an agent can use |
Test before going live
Use Test an agent for simulated caller suites, verdicts and browser voice sessions. Follow the publish-and-test recipe before promotion.
Try it in the browser
Note
POST /api/v1/test-sessionsstarts a browser voice test of one version of a phone agent: it admits awebrun marked as a test and returns a short-lived LiveKit room token, so you talk to the agent from Talos. The same Idempotency-Key rejoins the live test with a fresh token.
| Operation | MCP tool | Key role | Purpose |
|---|---|---|---|
POST /api/v1/test-sessions | test_sessions_create | member | Start a browser voice test of one agent version: admits a web run and returns a short-lived LiveKit room token (the same Idempotency-Key rejoins the live run with a fresh token) |