Agents through the API¶
The concept behind agents is explained in Agents.
On this page you store an agent through the API, add versions and call it from a request. An agent keeps the settings of a model call under a name: a model, instructions, a prompt version, skills and tools. Your application names the agent and supplies the input, so you update the settings without redeploying the application.
Your key needs the agents capability, and the capabilities of what the agent uses, such as
prompts for a stored prompt. You do the same with a mouse in
Agents in the console.
Each change creates a version. A request can use the latest version or pin a specific one. A field the agent fixes keeps the agent's value whatever the request sends; a request can add skills, tools and an instruction of its own.
Create an agent¶
curl -s -X POST "$CORE_URL/agents" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"name": "triage",
"description": "Classifies an incoming ticket.",
"model": "<a model id>",
"instructions": "Answer in one sentence.",
"prompt": {"id": "triage", "version": 1},
"skills": ["billing"],
"tools": [{"type": "toolset", "name": "support"}],
"temperature": 0.2}'
A successful request returns 201 and creates the agent with version 1.
| Field | What it does |
|---|---|
name |
unique in your organisation; what a call references |
description |
what this agent is for; for readers only |
metadata |
an opaque object of your own, at most 16 KiB serialised; Core stores and returns it and never reads it |
model |
the model id a turn runs on when the request names none itself |
instructions |
a standing instruction with developer authority; {{ $name }} marks an install value |
install_values |
optional: a value for every {{ $name }} in instructions, such as {"company": {"type": "string", "value": "Acme"}}, typed as on a prompt |
prompt |
{"id": "<prompt name or prm_ id>", "version": <number>}; version is required |
skills |
skill names or skill_ ids this agent offers |
tools |
tool, toolset and agent params; a function tool belongs to the call, not to the agent |
tool_choice, max_tool_rounds, temperature, max_output_tokens, text |
the same fields as on a request |
tool_search |
whether a turn this agent runs offers its tools directly or behind the tool router, as on a request; a request's own tool_search wins over it (An agent with many tools) |
tool_approval |
which tools wait for a person's approval before they run, by mode, as on a background request. A request can only make it stricter: a default or mode it sends looser than the agent's, or a longer wait, is ignored and named in the trace's interpretation.agent.overridden; a request that leaves default out takes the agent's. An agent tool or builtin__subagent runs without waiting only when every option naming it, the agent's and the request's, names it exactly with "never". An agent with tool_approval runs only in the background: on any other turn the request returns 400 on agent, or on model when the agent is named there |
memory_injection |
optional: which memory store a turn of this agent is given records from, in the key's memory.injection shape; it replaces the key's block as a whole for the turn, and "enabled": false gives the turn none (Give each assistant its own store) |
routing_config_id |
optional: the routing configuration a turn of this agent falls back along, one of your organisation's with a published version; it replaces the key's for the turn, and a request's own routing_config_id takes precedence (A configuration for one agent or one turn) |
live_checks |
at most 16 rules every turn of this agent is held to, on the caller's question or on the answer (Live checks through the API) |
blocked_reply |
the standard reply, at most 1000 characters, a turn answers with when a blocking input check fails; required as soon as one does |
recover_attempts |
how often an answer a check with recover failed is asked for again, 1 to 3; 2 when left out |
working_state_schema |
optional: a JSON Schema (Draft 2020-12) the conversation's working state is held to on a turn of this agent, checked when the agent is saved; null takes any JSON object, and a sealed copy reads it as null |
The name field is required; the definition fields are optional. An agent that fixes only a model and an instruction is as valid as one
that brings a whole toolset.
A {{ $name }} in instructions is an install value: a value you set once in install_values,
with its type (string, number or enum), which Core fills in before the instruction joins a turn, as part of the instruction itself. Every
{{ $name }} needs a value, every value must be used, and a value may not contain {{ or }}.
When you share the agent through a publication, whoever installs it fills in their own install
values (Sharing).
The prompt is looked up at creation and pinned to that version. If the prompt or the version does
not exist, or your key's policy does not name that prompt, you get the same 404 a turn naming the
prompt directly would get.
metadata belongs to the agent rather than to one version: a new version that leaves it out keeps
what it has, and {} clears it. Put whatever your own application needs there — an icon, a colour,
an internal reference — Core never looks inside it.
Add a version¶
curl -s -X POST "$CORE_URL/agents/agt_5Nj2.../versions" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<a model id>", "instructions": "Answer in two sentences."}'
Versions are numbered from 1 and cannot be changed after creation. A version is the whole definition: whatever you do not send again is not in that version.
| Call | Answers |
|---|---|
GET /agents |
your agents, each with its latest version in full |
GET /agents/{agent_id} |
one agent with its latest version |
GET /agents/{agent_id}/versions |
its versions, paged by version number, oldest first; add ?order=desc for newest first |
GET /agents/{agent_id}/versions/{version} |
one version |
DELETE /agents/{agent_id} |
204; every version goes with it |
Copy an agent¶
To start a new agent from one you already have, copy it under a new name:
curl -s -X POST "$CORE_URL/agents/agt_5Nj2.../copy" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"name": "support-desk-nl", "version": 2}'
The answer is 201 with the new agent, at its own version 1. Leave out version to copy the
latest version. The copy takes the version's whole definition and the agent's description, labels
and metadata; it pins the same prompt version, the same skill versions and the same agents as
tools, and copies none of them. It is your own agent from then on: changing it never changes the
original, and the original's later versions never reach it.
Your key needs to read the agent you copy and to be allowed to create an agent under the new name
with the same labels. A copy a publication installed sealed, and one whose publication does not
allow forking it ('fork': false), keeps it to its own agents or does not allow reusing it, cannot be copied: 403. An agent that names itself as a tool names the copy once copied.
Call an agent¶
{
"input": "My card was charged twice.",
"agent": { "id": "triage", "variables": { "ticket": "My card was charged twice." } }
}
Set id to the agent name or its agt_ id. Omit version to use the latest version at request
time, or specify it to keep using a particular version after new ones are published. variables
fills in the prompt this agent version pinned.
You can leave out model as soon as the agent fixes one. If neither the request nor the agent names
a model, the call is 400 with model in error.param.
A key whose policy sets settings.agents_required must name an agent on every turn, and no model is called
for a turn without one: POST /responses answers 403 with agent in error.param, and
POST /messages answers 403 whose message starts with agent: (Anthropic's error has no
param).
Such a key also runs its agents as defined, so the rule in the next section does not apply to it:
a turn that adds tools, skills, instructions or a prompt of its own (on POST /messages:
system, tools or skills) is 403 naming that field, the same way. Instructions identical to
the agent's own add nothing and pass, unless the agent is a sealed copy, which refuses any. A
catalogue model other than the agent's stays the 400 it is under any key; the temperature and
token limit may still be set where the agent leaves them open, and run on the agent's value where
it does not. Naming the agent as the model counts as naming it. A coding
harness that sends its own system prompt is refused on such a key: give it an ordinary key whose
policy reaches the agent instead.
Submitting tool outputs continues the turn it belongs to, so it passes when that turn named an agent
and is refused when it did not. A schedule under such a key must name an agent in its request, and
the key cannot call a tool directly. Naming an agent the key's
rules do not permit is still 404. See Keys.
To require an agent of some people only, write a forbid over the kind direct_turn with a
condition on the person instead (Stop a person chatting with a bare
model). Their turns that name no agent get the same
403 on agent, while their direct tool calls and knowledge searches stay allowed.
Name the agent as the model¶
A client that can only send a model, its own instructions and its own tools — a coding harness, an
SDK you do not control — reaches an agent through model instead: agent:<name>, or the agent's
agt_ id. GET /models lists every agent your key can use that way, after the models, with
owned_by: "agent".
from openai import OpenAI
client = OpenAI(base_url="https://<your-deployment>/api/v1", api_key="ack_...")
response = client.responses.create(
model="agent:triage",
instructions="You are running inside my editor.",
input="My card was charged twice.",
)
print(response.model) # the model the agent runs, never "agent:triage"
It is the same reference as the agent field and is refused the same way: 404 for an agent your
key cannot use. A bare name such as triage is never read as an agent, because it could be a
model, and neither is anything after agt_ other than an id Core gave out. The agent must fix a
model of its own — one that does not is 400 on model and is not listed; name it in agent
beside a model instead. If you send both forms they must name the same agent, or the call is
400 on model. The same works on POST /messages with the Anthropic SDK, where your system is
your own instruction.
A request adds, it does not override¶
The request's skills and tools are added to the agent's. If both name the same tool, it counts
once. Your instructions are added too: the model reads yours first and then the agent's, as two
separate instructions, and the response echoes yours alone. An instruction identical to the
agent's is sent once.
Any other field the agent version fixes — temperature, max_output_tokens, max_tool_rounds,
tool_choice, text, reasoning — keeps the agent's value. If your request sets one to a
different value, the turn still runs, on the agent's value, and the call's
trace names the field under interpretation.agent.overridden, so you can see
what was not used. The definition is the point of an agent; the turn is not refused so that a
client that always sends its own defaults can still use one.
{ "agent": { "id": "triage" }, "temperature": 0.9 }
If triage fixes a temperature of 0.2, this turn runs at 0.2 and the trace's overridden is
["temperature"]. To use a different value, create a new version of the agent or call a different
agent.
Two things are still refused. A model from the catalogue that differs from the agent's is 400 on
model: name the agent alone, or name it as the model. And your skill references and the agent's
together may be at most 32, the same ceiling a request carries on its own; more is 400 on
skills.
Sending nothing for a fixed field, null, "auto" for tool_choice (its default), or the same
value the agent fixes changes nothing and is not named as overridden, so a client that serialises
its whole request object works unchanged.
What the agent contributes is checked exactly like what you write out yourself. A skill, tool or prompt your key's policy does not name stays out of reach, even when the agent names it.
Give an agent to a turn as a tool¶
A turn can hand another agent to its model as one tool. Name it in tools, on the request or in an
agent's own definition — the second is how one agent delegates to another:
{
"model": "<a model id>",
"input": "A customer was charged twice.",
"tools": [{ "type": "agent", "name": "billing" }]
}
The model sees a tool called agent__billing, described by the agent's description. Its
arguments are the variables of the prompt the agent pins, each of the type and with the enum its
input schema declares, plus input: free text with what the
model asks of the agent. input is required only when the agent pins no prompt. version works
as on agent: omit it for the latest.
When the model calls the tool, Core runs one turn of that agent, with the agent's own model, instruction, prompt and skills, and hands the agent's answer back as the tool's result. That turn:
- runs under your key: every model, prompt, skill and tool it reaches is checked against your key's
policy, exactly as when you name the agent on
agentyourself; - gets the agent's own tools, except other agents,
builtin__subagentandcore__tools, so an agent called as a tool never calls another agent in turn. Agents may therefore name each other in a circle, or name themselves: Core saves that as written, and the circle is cut after one call; - counts as one level in a chain of turns calling turns. A chain that goes on through requests
-- an agent's tool reaching Core's own API, which starts a turn that calls an agent again --
stops five levels deep: there the model reads
This turn is too deep in a chain of calls to start another.as the tool's result, and the turn goes on (the limits oncore__tools); - has its own response id and its own usage, and the call's audit event names it as
agent_response_id; its model calls appear in the calling turn's trace; - may take up to two minutes by default. A failure, a refusal before it started, or running out of rounds reaches the model as one fixed sentence, never as an error on your request.
The agent's answer is treated like any tool's result: as information for the model, never as an
instruction. Your key needs agents, and your key's tools policy has to allow the name
agent__<name>.
An agent whose version carries background with mode allowed or always can also be started as
a task: the model gets a task id at once and the agent's answer comes back to the thread later. See
Run a tool in the background.
An agent that carries tool_approval cannot run as a tool: its turn cannot wait for a person, so it is refused before it starts and the model reads the one fixed sentence Agent could not be run. as the tool's result.
Deleting an agent another agent delegates to is 409, naming the agents that still use it.
The same tool is in your tool catalogue, so you can also reach it by name. GET /tools lists
agent__billing with the arguments above; a toolset pattern such as agent__* offers it to a turn
exactly as the agent entry does (an agent named both ways is offered once); and
POST /tools/agent__billing/call runs the agent's turn for you and answers its answer, which is
how an outside application or an MCP client served one of your
toolsets uses it. Only the agents your key may use are
listed or called; an agent that only runs in the background cannot be called outside a turn. See
Call a tool directly.
Errors¶
| Status | Cause |
|---|---|
404 |
no such agent, no such pinned version, an agent your key's policy does not name, or agents is not effective for your key — also for an agent named in tools |
403 |
the name you are creating falls outside what your key's policy names; nothing is stored |
403 on agent |
the turn names no agent while your key's policy requires one (settings.agents_required), or a forbid over direct_turn holds for the person the turn is for |
403 on tools, skills, instructions or prompt |
your key's policy requires an agent, and the turn adds that field to it |
409 |
the name is already taken — or, on DELETE /prompts/{id}, an agent version still pins a version of that prompt — or, on DELETE /agents/{id}, another agent still delegates to this one |
400 on model |
the request names no model and the agent fixes none, names a catalogue model other than the agent's, names as the model an agent that fixes none, or names two different agents in model and agent |
400 on agent or model |
tool_approval requires background: the agent carries tool_approval and the turn is not a background one; on model when you named the agent there |
400 on prompt |
the request names a prompt while the agent version already pins one |
400 on agent.variables |
variables were sent while this agent version renders no prompt, a variable the pinned prompt requires has no value, or a value does not fit its property in the prompt's input schema |
400 on skills |
your skills and the agent's together exceed 32 |
422 on live_checks, blocked_reply or recover_attempts |
a live check the version cannot hold; see Live checks through the API |
422 on install_values |
on a write: a {{ $name }} with no value, a value instructions does not use, a value containing {{ or }}, or a value that does not fit its type |
On a turn every one of these arrives in OpenAI's error object, with
the field in error.param; the same refusals of a write to /agents are problem+json with the
field in errors.
Deleting a prompt an agent version pins is refused: delete the agent first, and the prompt is free.
Track which agent ran¶
A stored response keeps the agent that actually ran — id, name and version — beside the skills and
tools the turn ran with; every call's trace carries the same as
interpretation.agent, with overridden listing the fields of your request it did not use ([]
when none). response.model is always the model that ran. A background run resumes on that same recorded
combination, so a new agent version does not change a run already under way.