Skip to content

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 agent yourself;
  • gets the agent's own tools, except other agents, builtin__subagent and core__ 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 on core__ 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.