Make a model call¶
Send POST /responses to run a turn with a language model. A turn includes the model call and any
tool calls needed to produce a response. This page describes the request, the response, streaming
and the errors you meet; every other page under Calling Core builds on it.
You need an API key and a model id that key's policy permits. If you have not sent a request yet, start at Make your first call.
Request fields¶
{
"model": "<a model id>",
"instructions": "You answer in one sentence.",
"input": [{ "role": "user", "content": "What is a bearer token?" }],
"temperature": 0.2,
"max_output_tokens": 400
}
| Field | Description |
|---|---|
model |
Model id. GET /models lists the models your key is allowed to use. |
input |
A string or list of items. A string is treated as one user message. |
instructions |
Instructions for this turn, with developer authority. |
temperature, max_output_tokens |
Optional settings passed to the model. A model that refuses a sampling parameter fails the turn instead of ignoring it: the current Claude models reject temperature, so leave it out for those. Gemini models accept it, except gemini-3.5-flash-lite and gemini-3.6-flash, which have fixed sampling and ignore it. |
text |
Output format, for example {"format": {"type": "json_schema", "name": "...", "schema": {...}}}. |
reasoning |
Optional. What this turn asks of the model's own thinking: {"effort": "low" \| "medium" \| "high" \| "xhigh" \| "max", "summary": "auto" \| "concise" \| "detailed" \| "none"}. Both parts are optional. An effort you leave out is not sent, so the model keeps its own default. summary asks for the thinking back in readable form; left out, your key's or organisation's setting decides (Response fields). "summary": "none" asks for no summary on this turn whatever those settings say, and every model accepts it: the model still thinks at its effort and the reasoning tokens still count in usage. A model reached over an OpenAI-compatible chat-completions API has no way to ask for a summary, and what comes back depends on the model's server (Response fields); a model whose client could never return one, an Azure deployment on chat completions, refuses a request that asks with a 400 naming reasoning.summary. A model that refuses an effort fails the turn, like a sampling parameter it refuses. |
agent |
Optional. Names a stored agent whose definition this turn is built on: its model, instruction, pinned prompt, skills and tools. Its skills and tools are added to the request's; any other field it fixes and the request also sets is refused with 400, naming that field in error.param. With an agent that fixes a model, model may be left out. |
timezone |
Optional, a Core extension. An IANA zone name such as Europe/Amsterdam: the timezone a prompt's built-in date and time values are read in for this turn. Leave it out and the timezone of the schedule firing the turn applies, otherwise your organisation's, otherwise UTC. A zone Core does not know returns 400 with timezone in error.param. |
tool_search |
Optional, a Core extension. {"mode": "router"} offers the model two tools, builtin__tool_search and builtin__tool_run, in place of the tools Core runs for this turn and your own function tools, so a turn can reach many more tools than a model accepts in one call; {"mode": "direct"} offers every tool as usual. top_k (1 to 20) and budget_tokens set how many tools one search returns and how large its answer may be. Left out, defer_loading: true on any tool or OpenAI's {"type": "tool_search"} entry asks for router (coding agents), and otherwise the agent's choice applies, then your key's. Your key's tooling.tool_search.enabled must be on, and your organisation must allow tool search, or router returns 400 with tool_search.mode in error.param. See The tool router. |
helper |
Optional, a Core extension. Names one of your own helpers, such as acme.summary, whose work this turn is: every usage row the turn writes carries the name, and without model (and with no agent fixing one) the turn runs on your organisation's helper model. Two to four dotted lowercase segments, at most 64 characters; names under assist. are Core's own. A name outside those rules returns 400 with helper in error.param. The response does not repeat it. See Build your own helper. |
metadata |
Up to 16 string key/value pairs, stored and returned without interpretation. |
tool_variables |
Optional, a Core extension. Values for the call variables the turn's tools declare, as a flat object of names to strings, numbers or booleans, such as your customer's account id or own token for your API. The model never sees them and the response never repeats them; each tool receives only the ones it declares, and a name no tool declares is ignored. A required one that is missing, or a value its declaration does not admit, returns 400 with tool_variables.<name> in error.param. See Pass a customer's value to a tool. |
hosting |
Optional. Requires the model answering this turn to run in a named place, for example {"region": "eu"}. region is a jurisdiction (eu, us, in-cluster, global) or a two-letter country code. Only an exact match on the model's own region or country satisfies it: nl does not satisfy eu, and eu does not satisfy nl. The requirement also holds every other model called for this turn: the one that summarises the thread's older messages, the embedding model a knowledge search uses, an image or transcription tool's model and a knowledge tool's model steps. Those do not fail the turn when they run elsewhere: the thread's messages are sent unsummarised, a knowledge search that needs the embedding model is unavailable, and a tool answers Denied by policy. The audio model that transcribes an input_audio part is the exception: when no audio model runs in the region, the request is refused 403. |
Input items support the roles user, developer, system, delegate and assistant. Put
application instructions in instructions or a developer message. Put end-user content in a
user message, including requests phrased as commands. Core labels this content untrusted and
never sends it as a system instruction; a key that holds
trust_envelope also tells the model that it is data.
A user message can also carry files, images and recordings as content parts: input_file and
input_image (Files), and input_audio, a
recording Core transcribes before the model reads it, with the audio model the part names in
model or else the first one your key admits (Transcribe
audio).
Choose a model¶
GET /models lists the chat models your key is allowed to target. The answer carries the same
models twice, for two kinds of reader:
| Field | What it holds |
|---|---|
items, next_cursor |
Core's own list, cursor-paged, with the full entry shown below |
object, data, has_more, first_id, last_id |
the list the OpenAI and Anthropic SDKs read, so client.models.list() works with either. data holds every chat model your key may target in one page, so has_more is always false |
An entry of data is short: id, object and type (both model), owned_by, display_name
(the id again), and created and created_at, which are always 0 and the Unix epoch because
Core records no creation time for a model. Read items for everything else. With ?kind=image,
?kind=embedding or ?kind=audio (below), data is empty and those models are in items only.
Each entry of items carries where the model runs, which lab trained it, the chain your turns on
it fall over along (fallback), and what it costs your organisation right now:
{
"id": "gpt-5.6-sol",
"object": "model",
"owned_by": "azure-openai-eu",
"region": "eu",
"country": "nl",
"lab": "openai",
"fallbacks": ["claude-sonnet-5"],
"price": {
"input_eur_per_million_tokens": "2.50",
"output_eur_per_million_tokens": "10.00",
"cached_input_eur_per_million_tokens": "0.25",
"cache_write_eur_per_million_tokens": null,
"eur_per_unit": null,
"effective_from": "2026-09-01T00:00:00Z",
"is_tenant_override": false,
"listed": {
"currency": "EUR",
"input_per_million_tokens": "2.50",
"output_per_million_tokens": "10.00",
"cached_input_per_million_tokens": "0.25",
"cache_write_per_million_tokens": null,
"per_unit": null
},
"exchange_rate": null
},
"deprecation": null
}
lab is the lab that trained the model — anthropic, openai, google, meta, mistral,
perplexity, black_forest or artific — as your operator states it, and null when none is
stated. It is not where the model runs: that is region, and owned_by is the provider serving it.
price is null when nothing prices the model; usage on such a model is still recorded and simply
costs nothing until a price exists (usage). Amounts are euro as
exact decimal strings. cached_input_eur_per_million_tokens is the rate for input the provider
read from its prompt cache and cache_write_eur_per_million_tokens the rate for input it wrote into
it (prompt caching); null means no separate rate, so those tokens cost the
plain input rate. eur_per_unit is set instead of the token amounts for a model charged per
unit: per image for an image model (?kind=image, below), per minute of audio for an audio model
(?kind=audio). is_tenant_override is true
when your organisation is charged its own price rather than the deployment's, and effective_from
is the instant the price in force began — a price announced for a future date is not shown here
until it applies.
listed is the same price in the currency it was recorded in, which is the currency the model's
vendor invoices in, with the same amounts minus eur_ in their names. exchange_rate is null for
a price in euro. For a price in another currency it names the rate Core converted by: the newest
reference rate of the European Central Bank and the day it was published.
"listed": {
"currency": "USD",
"input_per_million_tokens": "3.00",
"output_per_million_tokens": "15.00",
"cached_input_per_million_tokens": "0.30",
"cache_write_per_million_tokens": "3.75",
"per_unit": null
},
"exchange_rate": { "units_per_euro": "1.1650", "rate_date": "2026-10-08" }
units_per_euro is how many units of the currency one euro buys. Every euro amount is the amount in
listed divided by that rate, unrounded: 3.00 / 1.1650 for input here. So the euro amount changes
when the ECB publishes a new rate. How that carries through to what a month costs is on
A price in another currency.
deprecation is null unless your operator is retiring the model; see
below.
A retired model¶
A model being retired carries its date and replacement in items:
"deprecation": {
"effective_date": "2026-12-01",
"replacement": "claude-sonnet-5",
"in_effect": false
}
effective_date is the day (in UTC) from whose start the model no longer answers as itself.
replacement is the model that takes its place then, and in_effect says whether that day had
begun when you read the list.
Every response to a request naming such a model, before the date and after it, carries the header
X-Core-Model-Deprecation, a refusal included:
X-Core-Model-Deprecation: "gpt-5.6-sol"; effective="2026-12-01"; replacement="claude-sonnet-5"; rerouted
rerouted is there when the turn already ran on the replacement. When the request named more than
one retiring model, they follow each other, separated by commas. A streamed response carries the
header from the start of the stream, and a background run on the response that
creates it. A turn with no HTTP response of its own, such as a schedule's fire, carries no header;
there the reroute shows in the stored response's model. A web page cannot read this header; read
deprecation from GET /models.
From the date on, it depends on your organisation's and your key's policy (Retiring models):
- Reroute (the default): the turn runs on the replacement, as if you had named it.
modelin the response names the replacement, and the usage is costed at its price. When your key refuses the replacement, you get the status you would have got naming it yourself, with a message naming both models:The model 'gpt-5.6-sol' was retired on 2026-12-01 and its replacement 'claude-sonnet-5' cannot take this request: .... - Block: on
POST /responses,400withmodelinerror.paramand the messageThe model 'gpt-5.6-sol' was retired on 2026-12-01. Use 'claude-sonnet-5' instead.Name the replacement in your request.
This holds for every way a turn starts: POST /responses, POST /messages, a schedule's fire, an
agent calling another agent, and a task. A background run is rerouted at the moment it is
accepted, not when it is picked up: a background run accepted before the date and picked up after
it still calls the retired model. Close to the date, queue no more background runs on that model;
name the replacement.
Models your key reaches without naming them¶
A turn can call more models than the one in model: an image model through its tool, an audio
model that transcribes a recording, the embedding model that searches your knowledge, memory and
skills, and the model that summarises a long thread. Each is held to your key's regions, so each is listed with its region and country
too.
GET /models?kind=image lists the image models, each with the tool_name it is offered as
(builtin__generate_image__...). GET /models?kind=audio lists the audio models, each with the
tool_name of its transcription tool (builtin__transcribe_audio__...); the same models serve
POST /audio/transcriptions and input_audio (Transcribe audio).
GET /models?kind=embedding lists the embedding models. Unlike
the default kind=chat, these list every model your organisation has, and allowed says whether
your key's model policy admits it right now; false means a call on it is refused. For an image
model that is the model's half only: the tool must also be switched on and allowed
(image generation). Every chat entry has
kind: "chat" and allowed: true, because a chat model your key may not target is not listed.
GET /model-roles names the models Core picks for you:
{
"items": [
{"object": "model_role", "role": "embedding", "model": "text-embedding-eu",
"region": "eu", "country": "nl", "allowed": true},
{"object": "model_role", "role": "summariser", "model": null,
"region": null, "country": null, "allowed": null}
],
"next_cursor": null
}
summariser appears when your key has compaction and summarises (any mode but window). With
no compaction.model of your own it names the summary model your deployment is configured with,
if there is one; a model of null means the turn's own model summarises, and that one is decided
with the turn. Once your key's or your organisation's
monthly cap is reached, no new summaries are made and
the thread's older turns are sent as they are.
embedding appears when the deployment has an embedding model for your organisation. None of
these lists takes a request's hosting.region into account: that narrows one turn.
Response fields¶
{
"id": "resp_0KQ2mR...",
"object": "response",
"status": "completed",
"model": "<a model id>",
"output": [
{
"type": "message",
"role": "assistant",
"content": [{ "type": "output_text", "text": "..." }]
}
],
"usage": {
"input_tokens": 31,
"input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
"output_tokens": 22,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 53
},
"conversation": { "id": "thr_0KQ2mR..." }
}
There is no output_text field: the OpenAI SDK computes it from output, and so can you.
conversation is the thread a stored turn's items were written to, and null for a stateless
one. The id of the request that produced the answer is the X-Request-Id response header.
Read output as a list. It can contain assistant messages, function_call items for your
application to execute, mcp_call items for a tool Core ran for you, and a reasoning item
when the model's thinking came back with the answer. A background run that
waits for approval shows each waiting call as an
mcp_approval_request item, and a call decided after waiting names its request in
approval_request_id. That item carries text when a summary was
asked for and the model has a readable form for it. Core asks for one on every turn unless your
request, your key or your organisation says otherwise. Without text its summary is [], which
says the model thought and kept it to itself:
{ "id": "item_0KQ2mR...", "type": "reasoning", "summary": [] }
Models your deployment reaches through an OpenAI-compatible chat-completions API behave differently, because that API has no way to ask for a summary:
- A server that returns its model's thinking sends it whether or not anyone asked. Core puts
it where a summary would be: one
summary_textof the reasoning item, the same stream events, and athinkingblock onPOST /messages. It is the model's own thinking, word for word and as long as it ran, not a summary written for a reader. Core returns it only on a turn that asks for a summary; on any other turn it is dropped, though its tokens are still counted inusage. - A server that returns no thinking gives Core nothing to build the item from, so
outputhas no reasoning item at all, not one with an empty summary.
An Azure deployment declared on chat completions (the Azure provider of a deployment that does not
set the Responses API) is neither of these: it sends no summary and no thinking, so
a request that asks for a summary there is refused with a 400 naming reasoning.summary
(thinking on POST /messages) rather than answered without one. The summary Core asks for on
your behalf, when your key or organisation says so, is never refused, and neither is
"summary": "none".
Asking for a summary does not guarantee one. OpenAI writes reasoning summaries only for an
account it has verified. On a deployment whose OpenAI account is not, a turn that asks for a
summary on an OpenAI model still answers 200, without the summary text.
To get no summary on one turn, send "reasoning": {"summary": "none"} on POST /responses, or
"thinking": {"type": "adaptive", "display": "omitted"} on POST /messages. Both outrank your
key's and your organisation's setting. On an agent that fixes
reasoning, the agent's value runs instead and the response names reasoning as overridden.
Where an answer's sources are¶
When a turn used a knowledge tool or builtin__web_fetch, the sources travel in
two places.
The tool's own result lists everything it read, whether or not the model went on to cite it:
{
"type": "function_call_output",
"call_id": "call_0KQ...",
"name": "knowledge__handbook",
"output": "[1] …",
"citations": [{ "kind": "file", "ref": "chk_0KQ…", "title": "Handbook" }]
}
kind is url for a page the model fetched -- ref is then an address you can open -- and
file for a passage out of your own indexed content, where ref is the id of the chunk it came
from. A tool that reads no sources answers [].
That item is on the conversation, not in the answer: read it back with GET
/threads/{thread_id}/items (Continue a conversation). The answer itself
carries the round as one mcp_call, the Responses API's item for a tool the server ran for you,
and an mcp_call has nowhere to put a source list — which is why the annotations below are the
answer's own record of what it cited.
The assistant's text says where it cited them, as annotations on the text part:
{
"type": "output_text",
"text": "Renewal is yearly [1].",
"annotations": [
{ "type": "file_citation", "file_id": "chk_0KQ…", "filename": "Handbook", "index": 18 }
]
}
The offsets are into that same text: index for a file_citation, start_index and
end_index for a url_citation, which also carries url and title. They cover the [1]
marker itself, so you can turn it into a link without searching the text for it.
Offsets count Unicode code points, not UTF-8 bytes and not the UTF-16 units a JavaScript string
indexes by: in JavaScript, slice Array.from(text) rather than text itself when the answer may
hold an emoji or another character outside the Basic Multilingual Plane.
When a turn searches more than once, the numbers carry on: a second search after three passages
hands the model [4] and [5], so an answer citing both searches is annotated with each marker's
own source. A turn that runs in the background (background: true) carries no annotations; its
sources are on each result.
Core never guesses. A marker naming a passage the turn never retrieved gets no annotation and is left in the text as the model wrote it. A page the model read and never cited is on the result and in no annotation.
Prompt caching¶
A model provider can keep the start of a request it has just processed. When the next request begins with exactly the same content, the provider reads that part from its cache instead of processing it again, which is faster and cheaper. Storing it in the cache can cost extra.
How this happens depends on the model:
- Claude models. Core asks the provider to cache the start of the request for you, whenever a later call is likely to read it back: a turn that offers tools (every tool round sends the whole request again), every model call after the first within one turn, and every turn of a stored conversation. A single call without tools on a stateless turn is not cached. An entry lasts about five minutes after it was last read.
- Other models (OpenAI, including Azure OpenAI, Gemini and Scaleway). The provider caches on its own where it supports caching; neither you nor Core sends anything for it.
A request shorter than the provider's minimum is not cached at all, and costs nothing extra.
What the response tells you¶
usage.input_tokens_details says what caching did for this turn. Both numbers are already part of
input_tokens:
| Field | Meaning |
|---|---|
cached_tokens |
input the provider read from its cache, charged at the model's cached rate |
cache_write_tokens |
input the provider wrote into its cache, charged above the plain input rate; only Claude models report it, other models answer 0 |
The same figures are recorded as the usage kinds cached_tokens and cache_write_tokens, and
priced with the model's cached_input_eur_per_million_tokens and
cache_write_eur_per_million_tokens (what your usage cost). On
Claude, writing an entry costs about a quarter more than sending the same input once, and reading
it back costs a fraction of that, so caching pays off once an entry is read at least once.
Get more from the cache¶
The cache only matches identical content from the very start of the request, in the order tools, instructions, messages. A change early in the request starts the cache over for everything after it. Between the turns of one conversation, keep these the same:
instructions;- the agent and its version, when you name one;
- the tools and skills the turn offers, which a policy rule can also change;
reasoning.
Some things start the cache over whatever you send:
- a fallback to another model, because each model keeps its own cache;
- conversation compaction, which replaces the oldest messages with a summary;
- memory records Core adds to a turn that differ from the previous turn's;
- a pause of more than about five minutes between turns.
Response status¶
Check the response's status even when the HTTP request succeeds:
status |
Meaning |
|---|---|
completed |
The turn finished. If its last output item is a function_call, your application executes that tool and sends the result in the next turn. |
failed |
The model call failed or timed out. error.message contains a fixed Core message, not the provider's error text. |
incomplete |
The turn stopped before it was finished: the model reached its output limit, the turn reached its tool-round limit, a live check withheld the answer, or a background run reached its cost limit. incomplete_details.reason says which (below). |
requires_action |
A background run is paused until you submit tool results, or until you decide each call it holds for approval. |
queued, in_progress |
A background run is waiting or running. |
cancelled |
A background run or a streamed turn was cancelled. |
A model failure is reported in the response body because Core accepted the request and ran the turn. Validation and permission failures use HTTP error codes.
A cut-off answer¶
An incomplete response carries incomplete_details.reason:
reason |
What happened | What you can do |
|---|---|---|
max_output_tokens |
The model stopped at its output limit: your max_output_tokens, or the model's own maximum. The text written so far is in output, on a message item with status: "incomplete". A tool call that was cut off mid-way is not returned. |
Ask for a shorter answer, raise max_output_tokens, or continue from the text you have. |
max_tool_rounds |
The turn used all its tool rounds. | Raise max_tool_rounds, or send a follow-up turn. |
usage_cap_exceeded |
Your key's or your organisation's monthly cap was reached while the turn ran. What it produced until then is in output and is billed. |
Wait for the month to turn or have the cap raised; until then the next request answers 429. |
cost_limit |
A background run, the agent tasks it started and their runs together reached the run's cost limit. What was produced until then is in output and is billed. |
Send a new run with a higher cost_limit_eur, or continue from what you have. |
content_filter |
A live check of the agent withheld the answer. When every blocking output check of the agent is a pattern, a streamed answer stops where the forbidden text starts; otherwise, streamed or not, output holds no answer. The X-Core-Check header, or GET /responses/{id}/checks on a streamed turn, names the check. |
Show the person that no answer can be given. Ask your organisation's administrator about the check when it stops answers it should not. |
A streamed turn ends with response.incomplete carrying the same object. On POST /messages a
cut-off answer is stop_reason: "max_tokens", a withheld answer is stop_reason: "refusal", and a
turn out of tool rounds or stopped at a cap is stop_reason: "pause_turn": the vendor has no word
for a cap. A cost limit belongs to a background run, which POST /messages does not start.
Where your deployment's catalogue states a model's maximum output, a higher max_output_tokens is
lowered to that maximum when the model is called. The response still shows the value you sent.
Where your organisation has a routing configuration, a model that is unavailable does not end
the turn: Core runs it on the next model instead, and model names the one that answered.
See Fall back to another model.
Storage and HTTP status¶
For a non-streaming request, 201 with a Location header means the response was stored. Retrieve
it with GET /responses/{id}. A stateless call returns 200 and does not store conversation
content. Storage depends on the threads capability and store setting; see Continue a
conversation.
Stream the response¶
Set "stream": true to receive output as Server-Sent Events:
curl -N -X POST "$CORE_URL/responses" \
-H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
-d '{"model": "<a model id>", "input": "Explain bearer tokens.", "stream": true}'
Each event contains an event: line, a data: line with JSON and a blank line. The JSON includes a
sequence_number, starting at 0, that you can use to detect missing events.
| Event | Handling |
|---|---|
response.created |
Save the response id. |
response.in_progress |
Show that the turn is running. |
response.output_item.added |
Start an item at output_index. |
response.content_part.added |
Start the item's text part. |
response.output_text.delta |
Append delta to the text received so far. |
response.output_text.annotation.added |
One source the finished text cites, with the offsets citing it (above). |
response.output_text.done |
Read the complete text for the item. |
response.content_part.done |
Finish the text part. |
response.output_item.done |
Process the completed item, such as a function_call. |
response.completed, response.incomplete, response.failed |
Read the final response object. The stream ends here. After a cancel it is response.completed with status: cancelled. |
error |
The turn ran but could not be stored. Read code; no result was kept. |
Ignore comment lines such as : keepalive. They keep the connection open.
Model text and a reasoning summary are streamed in fragments. A thinking model's reasoning item
comes first: response.output_item.added, then response.reasoning_summary_part.added, one
response.reasoning_summary_text.delta per piece as the model thinks, then
response.reasoning_summary_text.done, response.reasoning_summary_part.done and
response.output_item.done, before the message begins. On POST /messages the same reasoning
arrives as a thinking block filled with thinking_delta events. A model that thinks again after
it has started its reply gets a second reasoning item: the message is finished first, the new
reasoning item is added at the next output_index and streamed the same way, and text that
follows it is a second message. output lists the items in that order, so read every message
item and join their text, as the OpenAI SDK's output_text does, not only the first one. On POST /messages that is a second
thinking block and a second text block. Refusals and tool calls arrive
as complete items, with response.output_item.added followed by response.output_item.done. If the model fails
during output, Core completes the current item with the text received, marks that item
status: incomplete, and ends with response.failed.
A started stream uses HTTP 200 and Content-Type: text/event-stream, including when the response
will be stored. The headers are sent before the turn runs. Retrieve a stored result using the id
from the first event.
Errors detected before streaming starts, such as an invalid request or disallowed model, still
return an ordinary HTTP status, with OpenAI's error object as the
body. Model failures after streaming starts appear as response.failed events.
If the connection closes, the turn continues and its usage is recorded. If storage is enabled, fetch
the result afterwards with GET /responses/{id}. To make the turn stop, cancel it
(below). Combining streaming with a background run
returns 400.
Use a client that supports Server-Sent Events. The generated Python client only supports non-streaming responses, and the Swagger UI explorer shows a body only once the response has finished. If an SSE client still receives every event at once, ask your deployment administrator to check whether a proxy is buffering them, or whether the model's vendor filters streamed output before releasing it (Azure OpenAI does unless the deployment's content filter runs in asynchronous mode).
Stop a streamed turn¶
A stop button in your application cancels the turn with the id from response.created. No extra
capability is needed: the key that started the turn may also stop it. Cancelling another key's
turn or a background run needs runs; without it you get 404.
curl -s -X POST "$CORE_URL/responses/resp_0KQ2mR.../cancel" -H "Authorization: Bearer $CORE_KEY"
Core stops the turn at whatever it is waiting on: the model still sending text, or a tool still
running. The answer is the stored response object with status: cancelled. It holds the items the
turn had already made. A message the model was still writing is in it with the text that had
arrived and status: incomplete. A client still reading the stream receives that same response as
the last event, response.completed.
What is billed:
- every round that had finished, as always;
- for the model call that was cut: what the vendor had already reported it using. Some vendors report the input tokens at the start, others only at the end. If the vendor had reported nothing yet, nothing is recorded for that call.
Cancelling again returns the same answer, without an error. If the turn had finished before the
cancel reached it, you get 409. If the turn has not stopped within a few seconds, you get 503;
just cancel again. A turn without storage has no output in the cancel's answer, because Core keeps
none. The stream itself shows it.
POST /messages has no cancel.
Common errors¶
| HTTP status | Cause |
|---|---|
400 |
The request was refused: a field Core cannot honour, a missing one, or one an agent fixes. error.param names it. On model it can also be a retired model your policy blocks. |
401 |
The API key secret is missing or invalid. |
403 |
The key's policy denies the model, the organisation is suspended, or a hosting requirement of the request or the key is not met (see below). |
403 on agent |
The key's policy requires an agent and the request names none. Name one of the key's agents. |
404 |
A referenced model, prompt, skill or file is unavailable, or its capability is disabled. |
409 |
The idempotency key was previously used for a stateless call. |
503 |
A required dependency is unavailable. |
Each of these carries OpenAI's error object rather than problem+json,
and so do GET /responses/{id}, POST /responses/{id}/tool_outputs and
POST /responses/{id}/cancel. See Errors, paging and retries for the fields and
retry handling.
Hosting refusals¶
A hosting requirement, or the key's regions_allow policy, fails with 403 and exactly one of
these four detail messages:
detail |
What you can change |
|---|---|
The requested hosting region is not allowed for this key. |
Request a region in your key's regions_allow policy (Keys), or drop hosting. |
Where this model runs is not known. |
This should not happen for a model GET /models lists; report it to your administrator. |
The model is not hosted in a region this key allows. |
Pick a model from GET /models whose region or country is in your key's list, or ask your operator to widen it. |
The model is not hosted in the requested region. |
Drop hosting.region, or pick a model that does run there (Models shows each model's hosting). |
Timeouts¶
A synchronous turn can run for up to five minutes, including tool rounds. Set your client timeout to accommodate this, or use a background run if you cannot keep the connection open.