An agent has two states at all times: a draft you edit, and a published version that live traffic runs. Every write — from the dashboard, the API, the MCP server or AgentCoPilot — lands on the draft. Nothing you change reaches a real caller until you publish. This is the operational concept the rest of the platform hangs off, so it is worth getting exact before you build anything real. GET /v1/agents/{agent_id} returns both: config is the draft, and published_version is the number live right now (null while the agent has never been published). Compare updated_at against published_at to tell an agent whose draft has moved on from one whose draft is what calls are running.

Publishing

POST /v1/agents/{agent_id}/publish runs publish-grade validation, freezes the draft as the next version number, and points published_version at it. It returns the version, the timestamp and any warnings validation raised — warnings do not block a publish, but they are worth reading. If validation fails, nothing is published and you get a 400 listing every problem. See validation for the full list of refusals and how to fix each one. Publishing needs the editor role. So does validate — both are POST endpoints, and a viewer gets a 403.

Order of operations with tools

Attaching an unpublished tool is rejected, so tools have to be published before the agent that uses them. The sequence is always the same.
1

Create or update the tool

POST /v1/tools or PATCH /v1/tools/{tool_id}, including its operation tree. This lands on the tool’s own draft.
2

Validate the tool

POST /v1/tools/{tool_id}/validate. Optional, but it tells you what publish will refuse.
3

Publish the tool

POST /v1/tools/{tool_id}/publish. Until this succeeds, the tool cannot be attached to anything — attaching it fails with tool 'name' has no published version - publish it before attaching.
4

Attach it to the agent

PATCH /v1/agents/{agent_id} with tools: [{"tool_id": "…"}]. Leave tool_version out — a draft tracks whatever is published now.
5

Validate the agent

POST /v1/agents/{agent_id}/validate.
6

Publish the agent

POST /v1/agents/{agent_id}/publish.
PATCH /v1/agents/{agent_id} takes the whole config, not a patch: anything you omit reverts to that field’s default, and omitting tools detaches every tool. Read the agent first, apply your change to the config you got back, and send the result. See agent overview.

Publishing an agent pins its tools

Publishing an agent records, for each attached tool and lifecycle hook, the tool version that is live at that moment. Republishing the tool afterwards does not change what live calls do. The agent has to be published again for the new tool version to reach a caller.
This surprises everyone once. Fix a bug in check_order, publish the tool, ring the number, and the agent still runs the old operation tree — because the agent version that answers still names check_order v3. The rule is worth the surprise: without it, publishing a tool would silently change the behaviour of every agent that attaches it, including agents you were not editing and did not test. The pin lives in the frozen config: a published version’s tools entries carry {"tool_id": "…", "tool_version": 3}, where the draft’s carry {"tool_id": "…", "tool_version": null}. Two agent versions with an otherwise identical config can therefore behave differently, and the pinned numbers are the only place that shows — which is why the dashboard’s diff has a Tools section listing v3 → v4 per tool. Two things do not pin:
  • Knowledge bases. kb_ids is frozen at publish like the rest of the config, but a knowledge base is not versioned — the agent reads whatever that base contains at call time. Attaching a new one still takes a publish.
  • MCP servers. mcps names an integration, and its allowed_tools is read live: narrowing it reaches every already-published agent on its next turn.

Version history

GET /v1/agents/{agent_id} returns versions: every publish, newest first, with its number, published_at and the email of whoever published it. (The list endpoint omits this — fetch the agent to get it.) GET /v1/agents/{agent_id}/versions/{version} returns one version’s frozen config in full: the prompt, the models, the turn handling and the tool versions it pinned, exactly as they were at that publish. In the dashboard, Version history in the agent editor puts any two of those side by side — including the draft, which is compared like a version even though it is not one. It opens on the comparison that matters most, what is live against what you are editing, and shows changed fields grouped by section (Identity & prompt, Models, Vision input, Turn handling, Tools & knowledge, Session behaviour) plus the per-tool pinned versions. Copy JSON on either side hands you the raw version response.

Rollback

POST /v1/agents/{agent_id}/versions/{version}/rollback puts an earlier version back into production. Precisely:
  • No new version is created. published_version moves to point at the older one. Version 5 does not become version 7; it becomes live again as version 5.
  • The draft is replaced by that version’s config, so any unpublished edits are lost.
  • The restored draft is unpinned. A frozen version pins its tools; a draft never does. Rolling back to a version that pinned check_order v3 gives you a draft attached to check_order at whatever is published now — so publishing that draft again pins the current tool version, not v3.
  • updated_at is set to that version’s published_at, not to now, so a rolled-back agent does not read as having unpublished edits.
  • It is refused with a 400 if that version cannot answer a phone number this agent is assigned to — see validation.
Rollback takes effect immediately and cannot be undone. Calls already in progress finish on the version they started; everything that starts afterwards runs the restored one.

What republishing reaches, and when

Two exceptions run through the whole table. A call, batch or conversation that was started with agent_version pinned, with an agent_override, or with an inline definition keeps what it was started with — see per-call configuration. And a call that is still in progress never changes, whatever else does. Phone numbers deserve spelling out: assigning a number is a one-time act. POST /v1/telephony/phone-numbers/{number_id}/assign requires the agent to be published and stores the agent id on the number. Republish the agent and live routing follows on its own — you do not re-assign, and there is nothing to redo. See phone numbers.
Agents are regional. An agent published against https://api.in.talqing.com does not exist in https://api.us.talqing.com, and neither do its versions. The same personal access token reaches both.

The full sequence

Create a tool, publish it, attach it, publish the agent.
Reading the history and rolling back:

Testing an edit without publishing

You do not have to publish to try a change. Any call-starting request can take agent_version: "draft", which runs the unpublished working copy for that one call — validated and tool-pinned at request time — while live callers keep hearing the published version. The same requests can also pin an older version number. See per-call configuration.

Endpoint reference

Deleting an agent deletes its published versions with it, permanently.

Validation

Every reason a publish is refused, and the fix for each.

Per-call configuration

Run the draft, pin a version, or override one field for a single call.

Publishing tools

Test runs, tool versions, and the pin-at-agent-publish rule from the tool side.