The HTTP API serves every operation of Auto Runtime. It covers playbooks, function and workflow definitions, runs and their history, and requests. It also covers events, analytics and the tool servers a playbook's functions may use. This page lists every route with its fields, limits and errors.
The MCP endpoints serve the same operations as tools. A definition's type is reasoning, interaction, prediction, recall, computation or workflow.
Send requests to the server's address, such as https://auto.example.com, the PUBLIC_URL of a team's runtime, with a credential.
- Org routes begin at
/v1/orgs/{org}. Playbook routes begin at/v1/orgs/{org}/playbooks/{playbook}. - Send the credential, an API key or a signed-in person's token, as
Authorization: Bearer <key>. Keep credentials out of prompts and source documents. - A server in local mode asks for no credential. The Sign-in and token reference gives the rules the server applies to credentials.
- Send command bodies as UTF-8 JSON with
Content-Type: application/json, at most 1 MiB. Compressed bodies are not accepted. - Query parameters are for
GETandDELETErequests, which take no body. A field may be supplied in only one place.
Each API key belongs to one org and has permissions for a set of playbooks:
| Permission | Lets the caller |
|---|---|
org:read |
Read the org's playbooks and connections, list models and providers, and search the org's tool servers |
org:write |
Create, update and retire playbooks, and add, reconnect and remove connections |
playbook:read |
Read definitions, runs, history, events, requests and analytics, and search and read tools |
playbook:write |
Create, change, retire or run a function or workflow, cancel a run, send or publish an event, answer a request and call a tool |
Each route table below names the permission a route needs.
When the server sends telemetry, every response except that of /health carries its trace id in the x-trace-id header. A request with a W3C traceparent header continues the caller's trace.
| Method and route | Returns |
|---|---|
GET /v1/openapi.json |
The OpenAPI 3.1.1 document of this API |
GET /v1/guides/{guide} |
One guide, as text/markdown |
GET /.well-known/api-catalog |
The API catalog of this server, as application/linkset+json |
GET /health |
Whether the server is up |
These four need no key. Where sign-in is on, the protected resource metadata needs no key either. It is served at /.well-known/oauth-protected-resource and under the path of each MCP endpoint. Where the runtime signs people in at an https PUBLIC_URL, the client metadata document, by which Auto names its own client when a person connects a provider by consent, needs no key either. It is served at /oauth/client.json.
The server builds the document when it starts, from its operations: the same operations its routes and its MCP tools serve.
- Each operation appears once. Its
operationIdis the operation's name, withsearch_in_orgfor the org'ssearch. Its summary is its title. Its tag names the resource it acts on. - Its description is its MCP tool's description. An operation served only over HTTP has no tool, so its description is its own: the five prediction routes, the five routes of Studio's connection page,
get_playbook,get_connection,get_run_history,get_playbook_analytics,validate,list_org_changes,list_playbook_changes,activate_allocationandreport_allocation. - Its input is in the path, and in the query string of a
GET. A command, or a query routed byPOSTsuch asvalidateanddry_run, takes it in the JSON body. The two uploads take a CSV dataset or a candidate's bytes as the body, and the document gives their maximum size; an upload takes the rest of its input, such as theformatof a candidate, in the query string. - Its schemas are JSON Schema 2020-12. Each has its own
$idunderhttps://on.auto/schemas/runtime/, with its definitions inside it. - Each problem an operation can return is listed under its status.
- The server URL is the relative
/v1, so the document stays valid behind any host.
A client library can be generated from the document, and a gateway that accepts OpenAPI documents can add the runtime from it. Each release's document is kept in the repository at docs/assets/openapi.json, so a client library can be generated without running a server.
GET /v1/guides/{guide} returns one of the guides that the MCP endpoints serve as guide://{guide}, with the same text. The guides say what the words mean, give the format of each definition type and the filters of every template, and hold the recipes for common tasks. The OpenAPI document lists their names. Each guide has its own path. So the server treats an unknown guide name like any other path it does not serve: without a key, the response is 401.
GET /.well-known/api-catalog returns a linkset in the API catalog profile. It lists the API at /v1, described by the OpenAPI document and documented by each guide with its title, and the API's status at /health.
- Every address is absolute. It is based on
PUBLIC_URLwhen that is set, and otherwise on the origin the request arrived at. - Behind a proxy, that origin is the scheme and host the server itself receives. The server reads no
X-Forwarded-*header. - Responses for the catalog,
HEADincluded, carryLink: <…/.well-known/api-catalog>; rel="api-catalog".GET /healthcarries the same relation, so a client given only the health address can find the catalog, and from there the document and the guides.
The document, the guides and the catalog change only when a new release of the server is deployed or the server is configured again.
- Each carries a strong
ETagof its bytes andCache-Control: public, max-age=300. - Each responds with
304and no body when the request'sIf-None-Matchnames its tag, a list that includes it, or*. HEADreturns their headers without the body./healthis never cached: it responds withCache-Control: no-store.
These routes are relative to /v1/orgs/{org}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
create_playbook |
POST /playbooks |
org:write |
playbook, name, optional description |
list_playbooks |
GET /playbooks |
org:read or playbook:read |
Optional include_retired |
get_playbook |
GET /playbooks/{playbook} |
org:read |
Playbook id in path |
update_playbook |
PUT /playbooks/{playbook} |
org:write |
name and description |
retire_playbook |
POST /playbooks/{playbook}/retire |
org:write |
Playbook id in path |
| Field | Rule |
|---|---|
playbook |
The id: 3 to 48 lowercase letters, digits and hyphens, beginning with a letter |
name |
1 to 100 characters, stored without the whitespace around it; it cannot be all whitespace |
description |
At most 2,000 characters, stored without the whitespace around it |
Each operation returns the playbook, with its id, name, description, status (active or retired), created_at, created_by, the caller who created it, by id and name, updated_at and, once retired, retired_at. list_playbooks returns them in playbooks.
list_playbookslists the playbooks the caller may access. It leaves out retired ones unlessinclude_retiredasks for them. It also accepts a key with onlyplaybook:read, so a key that may only read inside some playbooks can find them.- A key limited to a list of playbooks can create only a playbook whose id is on that list.
update_playbookrequires bothnameanddescription, even when only one of them changes. An update that changes nothing records nothing.- Retirement is permanent. A playbook id cannot be reused, and a retired playbook cannot be restored.
- A retired playbook stays readable:
get_playbookreads it,include_retiredlists it, and its definitions, runs and history remain readable. Commands inside it returnconflictwith the kindretired. Retiring it again succeeds and changes nothing.
| Case | Response |
|---|---|
create_playbook with an id the org has or had |
conflict |
update_playbook of a retired playbook |
conflict |
get_playbook, update_playbook or retire_playbook of an id the org does not have |
not_found |
| A change that meets another change to the org's playbooks at the same moment | conflict; send it again |
| Operation | Method and route | Permission | Input |
|---|---|---|---|
list_models |
GET /v1/orgs/{org}/models |
org:read |
Optional provider |
provider is a provider prefix of 1 to 32 lowercase letters, digits or hyphens, starting with a letter. The result is the same over HTTP and MCP; Model information describes its fields, aliases, wildcard entries and incomplete lists.
These routes are relative to /v1/orgs/{org}/playbooks/{playbook}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
create_definition |
POST /definitions/reasoning |
playbook:write |
name, source, optional overview |
list_definitions |
GET /definitions/reasoning |
playbook:read |
Optional include_retired |
get_definition |
GET /definitions/reasoning/{name} |
playbook:read |
Name in path |
update_definition |
PUT /definitions/reasoning/{name} |
playbook:write |
source, optional overview |
retire_definition |
POST /definitions/reasoning/{name}/retire |
playbook:write |
Name in path |
run_definition |
POST /definitions/reasoning/{name}/run |
playbook:write |
Optional input, optional UUID run_id |
get_run |
GET /runs/{run_id} |
playbook:read |
Run id in path |
The source is a reasoning function document. These rules apply to the definitions of every type:
- A name follows the playbook id rule: 3 to 48 lowercase letters, digits and hyphens, beginning with a letter.
- A name is unique within its type and playbook, and cannot be reused after retirement.
- A source document may be at most 65,536 UTF-8 bytes.
- Changing a document or its overview creates a version. Updating it with identical source, and the same overview or none, records no change.
- A run uses the latest active version.
- A retired definition can be read, but cannot be edited or run.
- A definition carries its
input_schemaandoutput_schemawhere its document declares them. A reasoning function, a computation function or a workflow whoseoutput.shapenames an output shape has the shape's schema as itsoutput_schema, and the shape's name asdetails.shape, such as"details": { "shape": "series" };detailsis left out otherwise. A prediction function has theseriesshape so, since its questions answer a series.list_definitionsshows both too.
A definition names the caller who created it as created_by, by id and name, and keeps it across its versions.
create_definition, update_definition, get_definition and retire_definition return the definition with its overview and its used_by; list_definitions leaves both out.
overviewis Markdown its author gives besidesourcefor the person who runs the business: why the definition exists, what it does and how it fits, in one to three short paragraphs, with a Mermaid diagram in a fenced block where a flow is worth a picture. It holds 1 character to 16,384 UTF-8 bytes. The version keeps it as written, and an update that leaves it out keeps the overview saved. A definition saved without one has none.used_bylists the active definitions of the playbook that use this one, read from the playbook each time the definition is returned, and is left out when none does. Each gives itstype, itsnameand itsrelation:callsfor a workflow whoserun_definitionsteps call it, with thosesteps, each itsnumber, its place among the workflow's tasks in the order of its document, and itsname;sends-buildsorasks-approvalfor a prediction function that sends it builds to train or asks it to approve a version;remembers-runsorremembers-changesfor a recall function with a filter on its runs (by theirsubject) or on its changes (by theirsource). A definition that uses this one in two ways is listed for each.
The interaction function send-build that a prediction function trains through, that a workflow calls in two steps and that two recall functions follow is returned with this used_by, shown here without its other fields:
{
"used_by": [
{ "type": "prediction", "name": "weekly-orders", "relation": "sends-builds" },
{ "type": "recall", "name": "build-answers", "relation": "remembers-runs" },
{ "type": "recall", "name": "build-changes", "relation": "remembers-changes" },
{
"type": "workflow",
"name": "weekly-build",
"relation": "calls",
"steps": [
{ "number": 1, "name": "send" },
{ "number": 3, "name": "again" }
]
}
]
}Interaction functions use the operations of reasoning functions, with interaction in place of reasoning in each route, such as POST /definitions/interaction and POST /definitions/interaction/{name}/run. The source is an interaction function document. Names, document size, versions and retirement follow the rules for reasoning functions.
A run responds with 200, status: started and its run_id. It then waits until its request is answered, expires or is cancelled. Two kinds of run finish within the request instead:
- a notification to the inbox;
- a function that calls a tool, whose output is the tool's result, or which ends in one of the endings its format lists.
| Case | Response |
|---|---|
A to or an argument the function cannot render |
conflict with the kind unworkable |
| A server or a tool the playbook may not use | unavailable with the kind tool_not_offered |
| The playbook has as many open requests as the runtime allows | unavailable with the kind requests_full |
The requests of a playbook have two routes of their own, relative to /v1/orgs/{org}/playbooks/{playbook}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
list_interactions |
GET /interactions |
playbook:read |
Optional to, type, function, limit and cursor |
answer_interaction |
POST /runs/{run_id}/answer |
playbook:write |
Run id in path; answer and an optional claimed_for |
list_interactions returns interactions, the playbook's open requests newest first, with has_more and next_cursor. They are the requests of interaction functions and of prediction functions' builds. An answered request leaves the list. Anyone who may read the playbook sees each request's party and message, just as they see a run's input.
tokeeps only the requests to one party.typekeeps only those from one type of function.functionkeeps only those from functions of one name. A name is unique within its type, sofunctionwithtypenames one function.
Each request has these fields:
| Field | Contents |
|---|---|
run_id |
The run that asked, which answer_interaction takes |
type |
The type of the function that asked |
function |
The name of the function that asked |
version |
The version of the function that asked |
to |
The party the request goes to |
delivery |
The server and tool the request is sent through, or null for the inbox |
message |
The message of the request |
takes_answer |
true for a question, false for a notification |
answer_schema |
The JSON Schema an answer must match, as the request recorded it when it was asked; null for a notification |
requested_at |
When the request was made, in ISO 8601 UTC |
expires_at |
When the request expires unanswered, in ISO 8601 UTC |
attempts |
The delivery attempts made so far |
conversation |
The conversation the playbook reads replies in, or null |
answerer |
The party whose reply counts, or null |
reply_refusals |
How many replies from the answerer the playbook refused |
standing |
in_inbox, to_deliver, delivering, delivered, retrying, undelivered, or cancelling once a cancel of its run was asked |
answer_interaction checks the answer_schema the request recorded. That holds even after the function has a newer version, whose output_schema is the one get_definition shows. A schema may be up to 64 KiB as JSON, so a page of 100 requests can contain up to 6.25 MiB of schemas. A smaller limit keeps a page smaller.
answer_interaction takes the run's run_id, an answer and an optional claimed_for:
answermust match the request's answer schema, and may be at most 64 KiB as JSON.claimed_fornames whom the caller says it is answering for, in at most 256 bytes. The runtime never checks it.
It records the answer on the run as one fact, request_answered, and returns that fact:
| Field | Contents |
|---|---|
run_id |
The run that asked |
answer_id |
The id of the fact |
answer |
The answer |
claimed_for |
Whom the caller says it is answering for, when given |
answered_by |
The caller who answered, by id and name; for a run answered by a reply, the playbook |
reply |
For a run answered by a reply, that reply's identity |
answered_at |
When the answer was recorded |
The run takes the answer within about a second (the server checks waiting runs every second by default), as Answering a request explains.
- A run of an interaction function succeeds with the answer as its
output, which reaches a workflow that waits for it. - A prediction function's build scores the candidates the answer names,
{ candidates }, each by the SHA-256 its upload returned. A candidate not uploaded to that build is refused asinvalid_input.
| Case | Response |
|---|---|
| An answer that does not match | invalid_input with pointers under /answer; the request stays open |
| The same answer again | The fact the run took |
| A different answer, an answer to a request that has ended or whose run was asked to cancel, or an answer to a notification | conflict |
| A run the playbook does not have | not_found |
When nobody answers a request before it expires, its run ends rejected, with the reason unanswered and the kind expired. A notification that could not be delivered ends with the kind undelivered. A request with the same run id and input then returns that ending as a 410 problem of the type https://on.auto/problems/unanswered. How a run ends lists every ending.
Computation functions use the operations of reasoning functions, with computation in place of reasoning in each route, such as POST /definitions/computation and POST /definitions/computation/{name}/run. The source is a computation function document. Names, document size, versions and retirement follow the rules for reasoning functions.
A run finishes within the request that starts it. Saving a definition checks its program, and refuses each problem under /source with its line.
| Case | Response |
|---|---|
| A ready checker does not finish the check within 2 seconds of the server's wall-clock time | invalid_input; nothing is saved |
| A save finds no checker ready | unavailable (503); the same call may be tried again |
| The program cannot produce its output for the input | conflict with the kind unworkable |
A program cannot produce its output when it:
- raised an error;
- returned a value JSON cannot represent, or an output larger than a run may record (see Runs and results);
- did more work or used more memory than a run allows.
The same input gives the same unworkable result again, unless the program reads the clock. Change the definition or the input rather than retrying.
Recall functions use the operations of reasoning functions, with recall in place of reasoning in each route, such as POST /definitions/recall and POST /definitions/recall/{name}/run. The source is a recall function document. Names, document size, versions and retirement follow the rules for reasoning functions.
A recall function keeps a view: a JSON value its module builds from the playbook's events, one event at a time. A run computes its answer from the view as it stands.
- A playbook may have at most 32 active recall functions. Saving one more returns
conflict. - A run finishes within the request that starts it.
get_definitionadds the view'sstanding.get_runadds to the run'srecordthe checkpoint the run read at, which says how far into the playbook's history the view had read. See How the view is kept.
| Case | Response |
|---|---|
| The view of the latest version is still being built | unavailable with the kind rebuilding and a Retry-After |
| The view has stalled | conflict with the kind stalled |
The module's answer cannot produce an output |
conflict with the kind unworkable |
Prediction functions use the operations of reasoning functions, with prediction in place of reasoning in each route, such as POST /definitions/prediction and POST /definitions/prediction/{name}/run. The source is a prediction function document. Names, document size, versions and retirement follow the rules for reasoning functions.
- A playbook may have at most 32 active prediction functions.
- A question finishes within the request that asks it, and the function's active version makes the prediction. A question such as
{ "series": "north", "horizon": 1 }answers a series whoseobservedline holds the last periods of the dataset and whosepredictedline holds the period asked, with itsvalue,lowandhigh; withouthorizonit holds every horizon the function names. The run'srecordholds the model'sversionand thecoverage. The input and the output has every field, and A question an answer in full. - A build (the input
{ build: {} }) responds with200andstatus: started, then waits for its trainer, as A build explains. get_definitionadds the dataset's and the models'standing.
Five routes are served only over HTTP; no MCP endpoint offers them as tools. They are relative to /v1/orgs/{org}/playbooks/{playbook}:
| Operation | Method and route | Permission | Body | Returns |
|---|---|---|---|---|
upload_dataset |
PUT /datasets/{sha256} |
playbook:write |
text/csv with Content-Length, at most 64 MiB |
{ dataset, bytes, rows, series, columns, period, ... } |
read_build_brief |
GET /runs/{run_id}/build/brief |
playbook:read |
None | The brief, text/markdown |
read_build_rows |
GET /runs/{run_id}/build/rows |
playbook:read |
None | The training rows, text/csv, streamed |
upload_candidate |
POST /runs/{run_id}/build/candidates |
playbook:write |
A model in a format the brief lists, named by ?format=, application/octet-stream with Content-Length, at most 64 MiB |
{ candidate, bytes, format, signature } |
activate_version |
POST /definitions/prediction/{name}/activate |
playbook:write |
{ version } |
{ function, version, rule, activated_at } |
- A body sent without its
Content-Lengthgets411. A body whoseContent-Lengthis larger than the route accepts gets413, before the body is read. - The build itself is answered through
answer_interaction, with{ candidates }: the SHA-256 of each candidate, asupload_candidatereturned it.
The three build routes work only on a build that is waiting for its answer:
| Case | Response |
|---|---|
| A run the playbook does not have | not_found |
| A run that is not a build | conflict with the kind not_a_build |
| A build that took its answer or ended | conflict with the kind build_ended |
upload_candidate refuses a candidate as invalid_input when:
- its
formatis not one a model runtime of the server executes for the function, at/format, naming the formats it takes, or it names none where the brief lists several; this is refused before the body is read; - the model runtime of its format cannot load it;
- that runtime finds its signature does not match what the function feeds it;
- the build already has the 8 candidates it accepts.
A candidate in a format whose model runtime needs a feature the runtime's license does not include is refused before the body is read, as conflict with the kind feature_unlicensed. Its detail names the feature and the format and says where an owner adds the feature, which resolve_at links, and the brief never lists such a format.
While every model process available to builds is busy, it returns unavailable with the kind model_process_busy and a Retry-After. The check shares those processes with the builds' scoring, and never takes one a question needs.
activate_version returns not_found for a version the function did not build, and conflict with the kind already_active for the version that is already active.
These routes are relative to /v1/orgs/{org}/playbooks/{playbook}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
create_definition |
POST /definitions/workflow |
playbook:write |
name, source |
list_definitions |
GET /definitions/workflow |
playbook:read |
Optional include_retired |
get_definition |
GET /definitions/workflow/{name} |
playbook:read |
Name in path |
update_definition |
PUT /definitions/workflow/{name} |
playbook:write |
source |
retire_definition |
POST /definitions/workflow/{name}/retire |
playbook:write |
Name in path |
run_definition |
POST /definitions/workflow/{name}/run |
playbook:write |
Optional input, optional UUID run_id |
get_run |
GET /runs/{run_id} |
playbook:read |
Run id in path |
cancel_run |
POST /runs/{run_id}/cancel |
playbook:write |
Optional reason |
send_run_event |
POST /runs/{run_id}/events |
playbook:write |
event with type, and optional id, source, subject and data |
The source is a workflow document. Names, document size, versions and retirement follow the rules for reasoning functions.
- A saved workflow has the
media_typeapplication/yaml. - Its
description,input_schemaandoutput_schemacome from the document, theoutput_schemafrom itsoutput.shapewhere it names one, withdetails.shape. - A workflow whose document has a
schedulealso hastriggers, inget_definitionandlist_definitions. They are the triggers its schedule names, in the order it names them.
| Trigger field | Contents |
|---|---|
kind |
event, cron or every |
reference |
/schedule/on, /schedule/cron or /schedule/every |
filters |
For an event trigger: its filters, each with its reference, type and attributes |
expression |
For a cron trigger: its expression |
time_zone |
For a cron trigger whose schedule names one: its time zone; without it, UTC |
milliseconds |
For an every trigger: its period |
get_definition of an active workflow with triggers adds triggers_since: the id of the record that saved its current version. From that record on, every run its triggers start is a run of this version. triggers_since is one id for the whole workflow. A trigger that a later version left unchanged continues from the record that first saved it in that form, which triggers_since does not show.
run_definitionreturns200as soon as the run begins, with itsrun_idandstatus: started. A run that ended before its first wait returns how it ended.- Read the run with
get_rununtil its status issucceeded,rejectedorfailed. - Read its steps with
get_run_history, which contains oneworkflow_input_appliedevent for each input the run took (see Run history and playbook events). - While the runtime is stopping,
run_definitionreturnsunavailable; try again shortly.
A workflow runs once for each run_id. Retrying under a run id says what a request with the id of an earlier run returns.
send_run_event delivers an event to a run that is still started. It returns the run_id and the delivered event, with these filled in:
id, which the runtime makes when you leave it out;source, which is/callers/and your caller id when you leave it out;time, when it was sent.
| Part of the event | Limit |
|---|---|
| The whole event | 256 KiB as JSON |
type, id |
256 characters each |
source |
A URI reference such as /ledger/eu, as for a published event; 1,024 characters |
subject |
1,024 characters |
data |
Nests at most 510 levels deep, so that the run can keep the whole event in a list |
A run accepts an event with a given id only once, so a request can be retried with the same id.
| Case | Response |
|---|---|
| A type or source the runtime reserves for its own facts, as listed under Publishing events | invalid_input |
Text a published event may not contain: a control character, a lone surrogate or a noncharacter, or a type, id or subject with no character other than a space |
invalid_input |
| The playbook has no running workflow with that run id, including one that has ended | not_found |
| The run cannot take the event at that moment, such as while the runtime is stopping | unavailable; try again shortly |
cancel_run needs playbook:write. It cancels:
- a workflow run that is still
started; - the run of a reasoning function that is still
started, whether its client is there or gone; - the run of an interaction function whose request waits;
- a prediction function's build, whether it is starting, waiting for its trainer or scoring.
It takes an optional reason, 1 to 1,024 characters with no control character. The run keeps it as the detail of its ending; without one, the detail says who asked.
- The request is recorded on the run at once, whichever server takes it.
cancel_runreturns200with the run as it stands: stillstarted, or alreadyrejectedfor a reasoning run the server that took the request was running. - Within a moment the run stops its tasks and cancels each run it waits for.
- It ends
rejectedwith the reasoncancelledand the kindrequested. Read it withget_run.
A reasoning run its client started is stopped at once in the server where it is going, each call it has in flight ended as interrupted; so is one a workflow's step started when its workflow cancels it. One a workflow's step started that cancel_run cancels, or one going in another server, stops before its next model call or tool call, so it may go on for as long as one call of it may take: a call it already sent still answers and its answer is recorded, and a run whose last model call answers ends with that answer. Either way, the run records the spending of its model calls so far, and no call of it is left started without an end. A reasoning run whose server stopped before the run met the cancel stays started, its cancel recorded, until a request with its run_id, or the workflow that started it, asks for it again, which ends it cancelled.
Asking again before the run has ended records nothing more.
| Case | Response |
|---|---|
| The playbook has no run with that id | not_found |
| The run has ended | conflict |
| The run ends within the request that started it | conflict |
These runs end within the request that started them, so they cannot be interrupted from outside:
- the run of a computation or recall function;
- the run of an interaction function that calls a tool;
- a prediction function's question.
A cancelled run's rejection names the kind of its cancellation:
| Kind | The run was cancelled because |
|---|---|
requested |
someone allowed to change the playbook asked, with cancel_run |
deadline |
the workflow that waited for it ran out of time |
overrun |
it ran as long as a workflow may run |
parent_ended |
the workflow that waited for it ended first, or the branch that waited for it lost a race |
A run records its run_id, type, name, definition_version, status, timestamps and started_by, the caller who started it, by id and name. A successful run includes output, and a rejected run includes a rejection. get_run also returns the detailed record. A run whose output takes an output shape says it as output_shape, such as "output_shape": "series" on a question to a prediction function; a build of a prediction function, and a run of a definition that declares no shape, has none.
Most runs finish within the request that starts them. These runs finish later, so run_definition returns them started, unless one ended before its first wait:
- a workflow's run, whose
recordis empty while it is in progress; - an interaction function's run that asks a person, or that sends a notification through a tool;
- a prediction function's build, the input
{ build: {} }, which waits for its trainer.
Read such a run again with get_run until its status is succeeded, rejected or failed. A cancelled run is rejected with the reason cancelled. Workflows and runs explains how a run waits and ends.
| What | Limit |
|---|---|
| Input | 256 KiB as encoded JSON, nesting at most 512 levels deep, as deep as a workflow can hold a value. A deeper one returns invalid_input at /input |
| Output and record together | 1 MiB (1,048,576 bytes) as JSON |
These limits apply independently of the request-body limit.
A run's output and its record share that 1 MiB. Each type of definition makes room for its record in its own way, so the largest output differs by type:
| Type | Largest output |
|---|---|
| Reasoning | What leaves room for the record. The record keeps less of the prompt to fit; an answer that leaves too little room ends conflict, unworkable |
Interaction with call |
1 MiB less the run's record, measured exactly |
| Computation | 1,048,320 bytes: the function keeps 256 bytes for its record |
| Recall | 1,046,528 bytes: the function keeps 2,048 bytes for its record, which also says where the view had read to |
| Workflow | 1,048,574 bytes; a larger output ends the run conflict with the kind oversized |
A function's output past its bound ends the run conflict with the kind unworkable. Each format page repeats the bound for its type.
run_definition takes an optional run_id, a UUID. Supply one to retry a request without doing the work twice, or to read the run later. The runtime keeps one run under each id.
A request that reuses an id for another definition or another input returns conflict. A request with the same definition and input gets what the run under that id recorded:
| The run under that id | A request with the same id and input |
|---|---|
| Succeeded | Returns the recorded output; nothing runs again |
Was rejected as invalid_input |
Returns the same rejection |
| Was cancelled | Returns its cancellation, cancelled (409) |
| Went unanswered | Returns unanswered (410), as A request nobody answers says |
| Still waits: a workflow run, an interaction function's request, or a prediction function's build | Returns the run as it stands, still started, and starts nothing |
Has no final result: it ended unavailable, conflict or failed, or it is started without waiting, such as after an interruption |
Runs the definition again, as a new attempt. The attempt may use the latest version, and records the one it used. A started run of a reasoning function goes on from what it recorded instead |
The last row has three exceptions.
A workflow runs once for its id. A request with the id of a workflow run that ended without a final result returns conflict with the kind unworkable, once its input passes the workflow's input schema, which is checked first. Its detail says to use a new run id.
A started run of a reasoning function is never started a second time. A request with its id goes on from what the run recorded, at the version it started with, and answers when the run ends. In the server where the run is going, the request waits for it to end. Elsewhere, while the run's last event, by the database's clock, is newer than the longest gap between two events of a run that is still going, the run may still be running, and the request returns conflict with the kind taken, saying so. A run performed again gives the rule, and what a model call or a tool call without an answer becomes.
A run that may have called a tool is never attempted again under its id once it ended. That is a run of a reasoning function with tools, or of an interaction function with call:
- A run that recorded a tool call and ended without a final result returns
conflictwith the kindtools_called. When every tool it called is one its server marks read-only, the rejection addsbecause: "only_read", and its words say nothing was changed. - A run of an interaction function with
callthat is stillstartedreturnstools_calledtoo, even before its first recorded call, since it may still be in progress. - A run that ended before its first call, for example with
tool_not_offeredortool_server_failed, runs again like any other.
After tools_called, read what the run called with get_run_history, and check its effects outside the playbook. Then start a new run under a new id, which calls the tools again. When a tool-calling run cannot finish gives the endings that come before it, tools_unfinished and effect_unknown.
A workflow step that calls a function meets the same rules when the server performs the step again, for example after a restart. A reasoning function's run goes on at once from what it recorded, since the workflow host knows the server before it stopped. An interaction function's run that may have called its tool raises tools_called in the workflow; see When the function's run does not succeed.
The runtime records one final result for each run id. That does not mean an external effect happened only once.
publish_event records an event in a playbook, such as a month closed in a ledger or a deal won in a CRM.
| Operation | Method and route | Permission | Input |
|---|---|---|---|
publish_event |
POST /v1/orgs/{org}/playbooks/{playbook}/events |
playbook:write |
event |
The body contains event, a CloudEvents 1.0 event:
| Attribute | Required | Contents | Limit |
|---|---|---|---|
source |
Yes | Where the event comes from, a URI reference such as /ledger/eu or https://acme.example/ledger |
1,024 characters |
type |
Yes | What happened, such as com.acme.ledger.month-closed |
256 characters |
id |
No | Unique among the events of its source; the runtime makes one when you leave it out | 256 characters |
time |
No | When it happened, in RFC 3339 such as 2026-10-01T09:00:00Z; when it is left out, the runtime uses the time it records the event |
|
subject |
No | What the event is about within its source | 1,024 characters |
specversion |
No | 1.0 |
|
datacontenttype, dataschema |
No | The media type of data, such as application/json, and the absolute URI of a schema it follows |
|
data |
No | Any JSON value | Nests at most 510 levels |
- The event, with its id and time filled in, may be at most 240 KiB as JSON.
datanests at most 510 levels deep, so that a run can keep the event in a list. A deeper one returnsinvalid_inputat/event/data.- Any other attribute is an extension, and an event may have at most 32. Its name is 1 to 20 lowercase letters and digits. Its value is a string, a boolean or an integer, and it is kept as given.
As CloudEvents requires:
- no string may contain a control character, a lone surrogate or a noncharacter;
id,typeandsubjectneed a character that is not a space;datacontenttypeis a media type;- a
timehas a second of 60 only at the end of a day in UTC.
POST /v1/orgs/acme/playbooks/finance/events
Content-Type: application/json
{
"event": {
"source": "/ledger/eu",
"type": "com.acme.ledger.month-closed",
"id": "2026-09",
"subject": "september",
"data": { "region": "eu", "revenue": 120000 }
}
}It returns 200 with the event's id and time, and recorded_at, when the playbook recorded it:
{ "id": "2026-09", "time": "2026-10-01T09:00:00.000Z", "recorded_at": "2026-10-01T09:00:00.000Z" }list_playbook_events shows each published event as an event_published event. A published event starts the workflows whose event trigger matches it, and reaches the runs listening for its type.
A playbook keeps one event for each source and id, so a request can be retried with the same id.
- Publishing the same event again records nothing, and returns the first
id,timeandrecorded_at. - A retry that leaves out
timeis the same event. - A retry that gives a
timeto an event first published without one is the same event too. It returns the time the runtime filled in the first time, not the one the retry gave. - Times are compared as instants, so
2026-10-01T10:59:00+02:00is the same time as2026-10-01T08:59:00Z. - A leap second counts as the first second of the next day, so
2016-12-31T23:59:60Zis the same time as2017-01-01T00:00:00Z. - A different event with the same
sourceandidreturnsconflict. - Without an
id, every request records a new event.
Some types, sources and attributes are reserved for what the runtime records itself. An event that uses one returns invalid_input:
| Reserved | Names | Refused at |
|---|---|---|
| Types | Every type the playbook's events show and every fact the playbook's capabilities declare, listed below | /event/type |
| Sources | Those beginning /runs/, /definitions/ or /callers/ |
/event/source |
| Attributes | causationid, correlationid, caller, definitionversion, depth, calldepth, calledby, triggerkind and triggerreference |
/event/ and the attribute's name |
The reserved types are run_started, run_deferred, attempt_resumed, run_succeeded, run_rejected, run_failed, run_cancel_requested, tool_call_started, tool_call_answered, tool_call_failed, model_call_started, model_call_answered, model_call_failed, request_answered, delivery_started, delivery_succeeded, delivery_failed, delivery_refused, reply_refused, telling_started, telling_succeeded, telling_failed, replies_read, reading_failed, interaction_requested, dataset_assembled, candidate_uploaded, candidate_refused, candidate_scored, candidate_disqualified, baseline_scored, baseline_not_walked, version_built, shadow_predicted, shadow_graded, shadow_passed, shadow_failed, version_activated, activation_refused, build_asked, build_ended, definition_created, definition_updated, definition_retired, event_published, workflow_input_applied, step_started, step_waiting, step_finished, step_failed, step_skipped and reaction_refused.
A run of a reasoning function also records each call of its model: model_call_started, then model_call_answered with the tool calls the model asked for, or model_call_failed with because. The run's model calls gives their fields.
The reserved attributes are the ones the playbook gives its own events; see the playbook's events. So a published event cannot claim a cause, a caller or a depth it does not have.
These routes are relative to /v1/orgs/{org}/playbooks/{playbook}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
list_runs |
GET /runs |
playbook:read |
Optional type, name, status, limit and cursor |
get_run_history |
GET /runs/{run_id}/history |
playbook:read |
Run id in path; optional order, limit and cursor |
list_playbook_events |
GET /events |
playbook:read |
Optional type, since, run_id, order, limit and cursor |
get_event |
GET /events/{event_id} |
playbook:read |
Event id in path |
A retired playbook stays readable: these reads work on it, while every change to it is refused with conflict. The playbook's own creation, changes and retirement are not playbook events; get_playbook shows them.
list_runs returns runs, newest first by when each run first started.
- A listed run has the fields
get_runreturns, withoutoutput,recordand the detail and issues of a rejection. - A rejection shows its
reason, withkindandbecausewhen the function gave them. statuskeeps only the runs whose status isstarted,succeeded,rejectedorfailed.typeandnamekeep only the runs of one definition.
get_run_history returns the events of one run in the order the playbook recorded them, oldest first unless order is desc. Each event comes after the event that caused it. The events are the facts the runtime recorded about the run: each start and how it ended. A run that does not exist in the playbook returns not_found.
Runs that use tools (a reasoning function's, and an interaction function's that calls a tool) also record each call:
tool_call_started, withread_only: truewhen the server marks the tool read-only, andconnection_idbesideserverwhen the server is a connection a person added;- then
tool_call_answeredwhen the tool answered, withis_errorwhen its result is an error of its own; - or
tool_call_failedwhen it never answered, withbecause:arguments_refused,server_failure,timed_out,cancelled, ortoo_largewhen the tool's result was larger than one call may return.
The run of an interaction function records its request and its deliveries. Each delivery attempt starts with delivery_started and ends with delivery_succeeded, delivery_failed or delivery_refused.
| Event | Shows |
|---|---|
interaction_requested |
The delivery, the party, the size of the message and the expiry |
delivery_started |
The tool, the connection's connection_id where its server is one, and the size and digest of its arguments |
delivery_succeeded |
Where the request landed and where its replies are read |
delivery_failed |
because, including too_large for a result larger than one call may return, and retry_after_ms when the server asked for a delay |
delivery_refused |
because, too_large or unworkable |
request_answered |
The answer the run took, claimed_for and, for a reply, its identity |
reply_refused |
Each refused reply's identity, never its words |
Each of the three endings of an attempt also shows the size and digest of the tool's result, when there was one. That result is the run's output, which its run_succeeded shows.
A workflow's history also contains one workflow_input_applied event for each input its run took. Each is followed by one event for each step that input moved, named for the state the input left the step in:
step_waiting,step_finished,step_failedorstep_skipped;step_startedfor a step that runs others, such as adoor afork, and was still running.
A step that starts and finishes in one input shows only step_finished.
list_playbook_events returns the events of the whole playbook, newest first unless order is asc. They include:
- definitions created, updated and retired;
- runs started and ended;
- tool calls, and tests of a tool;
- the playbook's reads of a conversation,
replies_readorreading_failed; - its tellings,
telling_startedand thentelling_succeededortelling_failed; - the inputs workflow runs took;
- the events published to the playbook.
| Filter | Keeps |
|---|---|
type |
One event type |
since |
What the playbook recorded from that time on, in either order |
run_id |
What one run and every run it started recorded, such as the functions a workflow called: its whole tree |
since is an ISO 8601 time with its offset, such as 2026-10-05T09:00:00Z. Its date must exist in the calendar and its hour must run from 00 to 23, so T24:00:00Z is refused.
A run that another run started belongs to the tree of the run at its top, so filtering by its own id returns no events. Read the tree from the id of the run you started.
Tool-call events identify the server, the tool, the sizes of the arguments and the result, and how each call ended. The playbook keeps the full arguments and result of every call, scrubbed of secrets, unless the operator turns record_content off for the server.
- The sizes are of the scrubbed text the playbook keeps.
- A kept text's SHA-256 digest,
arguments_sha256orresult_sha256, is the key it is kept under. It is present only when the text was kept. - The history shows
argumentsandanswerwhen each is at most 2 KiB, and otherwise their sizes.get_eventreads one event with both in full. - Anyone with read access to the playbook can read that history.
A started call without an answer may have had an external effect; absence of an answer does not prove it was cancelled before acting.
Each event has these fields, and no others:
| Field | Contents |
|---|---|
id |
The event's id, a UUID that stays the same on every read; a step's is its input's id, an underscore and a number |
type |
What happened, such as run_started, delivery_failed or step_finished |
summary |
A sentence in plain language |
data |
The fact itself, at most 4 KiB as JSON |
metadata |
Where the event is recorded, when, who acted, and which definition, run and chain it belongs to |
metadata always has the first seven fields below, and each other field only when the event has it:
| Field | Contents |
|---|---|
stream |
The stream the event is recorded in, such as playbook/acme/finance/runs/<run id> |
position |
The event's number in its stream, from 1 |
global_position |
The store's sequence across every playbook, with gaps; it is not the order a page reads events in |
correlation_id |
The id of the run at the top of the chain the event belongs to, or null |
causation_id |
The id of the event that directly led to this one, or null when nothing recorded did, such as for a run you started |
at |
When it happened, by its own clock, in ISO 8601 UTC |
by |
Who acted: a caller's id, playbook: and the playbook's id for the playbook's own work, or runtime for what the runtime records of its own accord, such as a connection whose credential it found it cannot read |
run_id |
The run the event belongs to, when it belongs to one |
definition |
The type, name and, when the event names one, version of the definition the run carries out or the event is about |
called_by |
On a run a workflow's call started: the calling run's run_id, the reference of the call in its document, and run, which run of the call it is |
call_depth |
On a run a call started: how many calls are above it |
depth |
On a run a trigger or another run started, and on an event a workflow emitted: how many reactions are above it |
trigger |
On a run a trigger started: its kind, event, cron or every, and its reference in the document |
trace_id, span_id |
The trace and span of the append that recorded the event, one pair for every event of one append; with telemetry on, the trace the server exported |
data keeps each event within 4 KiB:
- Large values are shown in full when each is at most 2 KiB as JSON. They are an input, an output, a record, an answer, a call's arguments and result, a model call's messages, response and tool calls, and a published event's data. A larger one is shown as its size in bytes, under its name plus
_bytes, such asoutput_bytes. - When the event's data would still be larger than 4 KiB, its largest field is shown as its size first.
- A definition's document and schemas appear as their sizes. Its description shows its first 300 characters, and its warnings as a count.
- A rejection shows its reason, its detail shortened to fit, and for invalid input the number of issues and the first five.
- A rejection that kept a record shows the record as a success does. A run of a reasoning function keeps one when it is rejected after its model answered.
The full values are read elsewhere: get_run returns a run's output and record, get_definition a definition's document and schemas, and get_event one event with every field in full.
Some events show more in data:
workflow_input_appliedshows the kind and key of the input and how many steps it moved. It lists the first five, each with its task, run and outcome. For a function's rejection that has a kind and abecause, it shows both, and its summary says them in words. It also shows the kinds of what the run did next. It never shows the run's data.- A step event shows the step's
nameand itsreferencein the document. It also showsrun, which counts each time the step starts, andtimes, which counts how often it moved that way in that run. step_waitingadds what it waits for,call,timer,eventoradmission. For a call it adds therun_idof the run it started, whoserun_startednames thatstep_waitingas its cause. Foradmission, a call the meter would not start for now (When the runtime will not start the run for now), it adds the refusal'sdetail, itskind, itsbecause, itsresolve_at, itslifts_atand itslimited_becausewhere it gave them,retry_at, when the step tries again, anduntil, when it stops waiting, both in ISO 8601 UTC; its summary says what it waits for, why, when it tries again and stops waiting, and where to fix it.step_failedadds how it failed, and its error's type and title.event_publishedshows the published event'sevent_id,event_type,source,subject,timeanddata, and which attributes the runtimefilledin.reaction_refusedshows how many times in theminutea trigger did not start its workflow, ascount, and the lastreason.
Some events say more in their metadata:
- For an event a workflow emitted,
metadatanames the run inrun_id, the workflow and its version indefinition, and its depth in a chain of triggers indepth. - A
reaction_refusedevent'smetadatanames the workflow and the version whose trigger refused, indefinition. - A run that a trigger started shows
playbook:and the playbook's id asby. Every event of it shows thetriggerthat started it in itsmetadata, with itskindandreference. Itsrun_startedsays in words which kind it was, such as "was started by its every schedule".
Each event's causation_id links the events of a run into a graph.
- A run you start has no cause.
- A workflow run's start is followed directly by its inputs and their steps. Its first input is caused by its start.
- A step's event has the id of the input that moved it, an underscore and its number among the steps that input moved, such as
<input id>_2. - A step's event is caused by what came before it in that input:
- the input, for the first step;
- the step before it in its list;
- the
switchthat chose it; - the
forkit is a branch of; - the failed attempt a retry repeats.
- A step that continues from an earlier input is caused by the input that moved it on. That input (a call's result, a delivered event or a fired timer) is caused by the input whose step waited for it.
- A run that a step's call started is caused by the input that started the call, and the run's end by the input that ended it.
- A run that an event trigger started names the event's record as its cause.
- A run that a schedule started names as its cause the record that saved its schedule in its current form. That record comes before the record of the run's
definition_versionwhen a later version left the schedule unchanged.
get_event reads one event in full by its id, as get_run_history and list_playbook_events show it. It has the same type, summary and metadata. Its data has every field in full. A call's arguments and result are read from what the playbook kept, and answer is the document read from the result. Use it to open a tool's result, or an input or output too large for a page, from the history.
- A step event's id goes in the path unchanged, such as
GET /events/0199a3c4-7d2e-7c1a-9b3f-2f1e0d9c8b7a_2. - Its number counts from 1 without a leading zero, so
_0and_02are refused withinvalid_input. - An id the playbook does not have, whether of another playbook or of a step its input did not move, returns
not_found.
Over MCP, a field that may be large is shown in full only up to 64 KiB, as the MCP reference says.
Every page carries has_more and next_cursor, on the page and not on its events. Pass next_cursor as cursor to read the next page, until next_cursor is null.
limitis 1 to 100, and 20 when left out.limitcounts the events a page returns, step events included. A page can end between the step events of one input; itsnext_cursorcontinues from the next one.- Cursors are opaque. A cursor this playbook did not give returns
invalid_inputat/cursor.
A page can contain fewer items than limit, or none, while has_more is true:
- records with no event type are left out;
- a page stops after loading 4 MiB of stored data;
- with
status,typeorname, a page stops after looking at 1,000 runs or records.
A client learns of changes by asking what changed since the cursor of its last answer. The runtime holds the request until something changes, so the client does not poll. These two routes are served over HTTP alone; no MCP tool serves them.
| Operation | Method and route | Permission | Input |
|---|---|---|---|
list_org_changes |
GET /v1/orgs/{org}/changes |
org:read or playbook:read |
Optional since and wait |
list_playbook_changes |
GET /v1/orgs/{org}/playbooks/{playbook}/changes |
playbook:read |
Optional since and wait |
Each returns cursor, changes and more:
{
"cursor": "WyIxMjMiLCIwNmJhYjQ1YS0wNTVhLTU4ZTQtOWQ0Zi1mNmRkYTk0MDJjZjgiXQ",
"changes": [
{ "kind": "definition", "type": "reasoning", "name": "summary" },
{ "kind": "run", "run_id": "0199f7a2-6c1e-7b3a-9d40-5e8f1a2b3c4d" }
],
"more": false
}- Without
since, the response comes at once, with the cursor of now and no changes, so a client starts from now. - A change names only what the caller may read, as the routes that read it decide:
list_org_changesnames a playbook only to a caller that may access it, aslist_playbookslists it, and a connection only to a caller withorg:read. What the caller may not read ends no held request, and a response that lists nothing and says nomoregives back the cursor it was given, so the caller learns nothing of it. - With
since, the cursor of an earlier response, the response comes at once when something changed after it. Otherwise the runtime holds the request until the next change, or untilwaitseconds pass and it answers no changes with the cursor it was given. A server that stops answers every request it holds at once, the same way. waitis 0 to 25 seconds, 25 when left out. A larger or negativewaitreturnsinvalid_inputat/wait.cursoris opaque: the ledger's point after the last change listed. Give it back assince. A cursor the ledger does not hold, such as one of another database or of another playbook, returnsinvalid_inputof the typehttps://on.auto/problems/cursor_not_held; ask again withoutsince.changeslists at most 200 resources, in the order the ledger recorded what changed them, each once; one record may name several. Whenmoreistrue, more changed already: ask again at once with the newcursor.- Each change names a resource and its identity, nothing else. Read the resource through its own route.
| Route | kind |
Identity | What names it |
|---|---|---|---|
list_org_changes |
playbook |
playbook |
A playbook created, updated or retired |
list_org_changes |
connection |
connection |
A connection added, connected, broken or removed |
list_playbook_changes |
definition |
type, name |
A function or workflow saved or retired, or a prediction function's versions |
list_playbook_changes |
run |
run_id |
A run started, given an input or an event, or ended |
list_playbook_changes |
interaction |
run_id |
Everything the run of an interaction function records: its request opened, answered, expired, undelivered or cancelled, beside the run |
list_playbook_changes |
event |
event_id |
An event published to the playbook, a refusal of its reactions, a call of a tool by hand and a telling or a read of a conversation, by the id list_playbook_events lists it by |
A run's own records, its steps and its tool calls name the run; read them with list_playbook_events given its run_id. A new link or consent of a connection names nothing until the connection is connected.
| Operation | Method and route | Permission | Input |
|---|---|---|---|
get_playbook_analytics |
GET /analytics |
playbook:read |
Optional days, or from and to; optional type and name |
The route is relative to /v1/orgs/{org}/playbooks/{playbook}. get_playbook_analytics returns what the playbook's runs did over a window of days, in UTC.
| Parameter | Rule |
|---|---|
days |
7, 14 or 30: the last days, ending today. 7 when nothing is given |
from, to |
The first and the last day, as YYYY-MM-DD, both included: at most 366 days, with to not before from and not after today |
type, name |
Keep the runs of one definition, as they do for list_runs |
- A day must exist in the calendar, so
2026-02-30is refused rather than read as 2 March. - These are refused with
invalid_input, pointing at the parameter:dayswithfromorto,fromwithoutto, any otherdays, and any other parameter.
GET /v1/orgs/acme/playbooks/sales/analytics?days=7&type=reasoning
Authorization: Bearer <key>{
"days": 7,
"runs": { "total": 12, "succeeded": 9, "failed": 1, "rejected": 2 },
"tokens": { "input": 18400, "output": 2950, "cached": 12000 },
"duration_ms": { "p50": 1840, "p95": 6210 },
"by_day": [
{
"day": "2026-09-30",
"runs": { "total": 0, "succeeded": 0, "failed": 0, "rejected": 0 },
"tokens": { "input": 0, "output": 0, "cached": 0 },
"duration_ms": null
}
],
"by_function": [
{
"type": "reasoning",
"name": "triage",
"runs": 12
}
]
}The example shortens by_day. It holds every day of the window, oldest first, including days with no runs.
| Field | Contents |
|---|---|
days |
How many days the window covers |
runs |
The runs that ended, by how they ended, and their total |
tokens |
The input and output tokens the models of reasoning functions used, a rejected run's included when its model answered before the rejection; cached is the part of input read from the provider's cache; 0 where nothing was recorded |
duration_ms |
p50 (the median) and p95 (the 95th percentile) of how long the runs that succeeded or failed took, in milliseconds from their latest start to their end; null when no run of the period has a duration |
by_day |
The same runs, tokens and duration_ms for each day |
by_function |
Each definition by type and name, with runs, how many of its runs ended; the most runs first, then by type and name |
How runs are counted:
- A run counts on the day it first started, also when it was started again later.
- A run counts once it has ended:
succeeded,failedorrejected. A run still going counts nowhere. - A run started again under its
run_idcounts once. Its tokens add up over every attempt that ended, while its duration is that of its last attempt. - Rejected runs count in
runsand intokens, never induration_ms. - Workflow runs count in
runsand induration_ms, from when the run started to when the workflow ended. - A percentile is the nearest rank: the duration at place ⌈p × n⌉ of the n durations in order.
The result comes from a table the runtime updates as each run is recorded, so it is as current as the runs themselves. A playbook that does not exist returns not_found; a retired playbook works like any other.
Five operations find and call the tools of the tool servers of a playbook: those the operator set up, and the connections people added to its org, MCP servers and HTTP APIs described by an OpenAPI document alike. A reasoning function names their tools in tools as server/tool or server/*, a connection by its name. These routes are relative to /v1/orgs/{org}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
search |
GET /tool-servers/search |
org:read or playbook:read |
Optional playbook, terms, server and only |
search |
GET /playbooks/{playbook}/tool-servers/search |
playbook:read |
Playbook id in path; optional terms, server and only |
introspect |
GET /playbooks/{playbook}/tool-servers/{server}/tools/{tool} |
playbook:read |
Server and tool in the path |
validate |
POST /playbooks/{playbook}/tool-servers/{server}/tools/{tool}/validate |
playbook:read |
Server and tool in the path; arguments in the body |
dry_run |
POST /playbooks/{playbook}/tool-servers/{server}/tools/{tool}/dry-run |
playbook:read |
Server and tool in the path; optional arguments |
execute |
POST /playbooks/{playbook}/tool-servers/{server}/tools/{tool}/execute |
playbook:write |
Server and tool in the path; optional arguments |
executeneedsplaybook:writefor every tool, since a call acts as the playbook, under the credential of the server's entry or of the connection.validateanddry_runare routed byPOSTbecause theirargumentsare an object a query string cannot carry. They are still reads: they change nothing, call no tool and need onlyplaybook:read. A query string beside their body returnsbad_request.- The four reads ask each server they need for its tool list when you call them, as a run does, with the same time limits to connect and to list. They record nothing. An HTTP API is not asked, since its OpenAPI document is the list.
- On a playbook route, a playbook that does not exist returns
not_found. A retired playbook serves the reads like any other. It refusesexecutewithconflictof the kindretired, as it refuses every command.
search returns the servers of the playbook, those set up in the configuration and the connections of its org that serve it, and the tools of each that its entry allows.
- Without
terms, it lists every tool when the servers offer at most 100 in total, in the order of the servers and of each server's own list. When they offer more, it returns only the servers, each with its count of tools; search them withterms. termsis up to 8 words of up to 64 characters each, separated by spaces. Tools with a score come best first, those with the same score by name and then by server, at most 100.only: readkeeps the tools their server marks read-only, andonly: writethe others.serverkeeps the server of that name and asks no other. A name no server of the playbook has returnsinvalid_inputat/server.
Each word of terms, and each part of it split at _, ., - and case changes, scores in any letter case:
| Where the word appears | Score |
|---|---|
| A word of the tool's name, split the same way | 3 |
| A word of the name of one of the tool's arguments | 2 |
| A word of the tool's description | 1 |
GET /v1/orgs/acme/playbooks/sales/tool-servers/search?terms=invoice
Authorization: Bearer <key>{
"servers": [
{ "name": "billing", "type": "openapi", "record_content": true, "provider": null, "tool_count": 5 },
{
"name": "notes",
"type": "stdio",
"record_content": true,
"provider": null,
"unavailable": "The tool server notes could not be used: The tool server could not be reached",
"because": "unreachable"
}
],
"tools": [
{ "server": "billing", "tool": "getInvoice", "summary": "Reads one invoice.", "read_only": true },
{
"server": "billing",
"tool": "listCustomers",
"summary": "Lists the customers, with the invoice each owes.",
"read_only": true
}
],
"scopes": ["billing", "notes"]
}| Field | Contents |
|---|---|
servers |
The servers searched, sorted by name |
tools |
The tools found |
scopes |
The names of the servers searched |
providers |
On the org route, where the caller may add a connection: the providers it may connect that no server serves yet, as below |
Each server has these fields:
| Field | Contents |
|---|---|
name |
The name a function writes before the slash |
connection |
For a connection a person added, its id; absent for a server of the configuration. A connection whose name the configuration now sets up is listed beside that server, unavailable |
provider |
The name of the provider a connection was added from, or an entry of the configuration names, or null for a server given by hand |
type |
mcp for a remote MCP server, stdio for an MCP server the runtime starts as a process, or openapi for an HTTP API described by an OpenAPI document |
record_content |
Whether the content of its calls is kept |
tool_count |
How many tools of the server the operator allows, of those only keeps |
unavailable |
Instead of tool_count: why the server could not be asked for its tools, in words cut to 1 KiB. The other servers are still searched |
because |
With unavailable: key_refused when the server did not accept the key the runtime gives it, which only the operator can fix; scope_lacking when it answered 403 for a scope its credential lacks, which the words name, so asking again will not help until a credential is granted it; failing, rate_limited or unreachable when asking again later may work; for a connection, connection_pending, connection_broken, connection_not_allowed or connection_unavailable, as below |
playbooks |
On the org route only: the playbooks of the org whose functions may use it, or ["*"] for every playbook of the org |
Each tool has these fields:
| Field | Contents |
|---|---|
server |
The server's name |
tool |
The tool's name |
summary |
The first sentence of its description, cut at 300 characters |
read_only |
Whether its server marks it read-only |
What the result never contains:
- A server set up for another org, or for other playbooks of the org, is neither searched nor asked.
- The runtime adds no header, environment value, address, command or credential of a server to the result.
- What a server returns, such as its tools' names, descriptions and schemas, is passed on with two kinds of value scrubbed out. They are the values of the operator's
${...}references and the tokens minted for a server. A referenced value shorter than 8 characters is not scrubbed.
On the org route, search covers the org's playbooks. So you can see what the operator set up before a playbook exists, or without naming one:
- Without
playbook, it searches every server set up for the org, each withplaybooks. - With
playbook, it returns only the servers that serve that playbook, as the playbook's own route does, withplaybooksincluded. - A key limited to some playbooks must pass
playbook, and sees inplaybooksonly the playbooks it may access. Withoutplaybookit is refused withforbidden, whose detail says to name one of its playbooks inplaybook. - A
playbookthe org does not have returnsnot_found, as the playbook's own route does, and no server is asked. - A
serverthat no server of the org, or of the playbook given, has returnsinvalid_inputat/server.
On the org route, search also returns providers: the providers the caller may connect here that no server of the org serves yet, each with its name, which add_connection takes as provider, its title and its description. So an agent asked for a service that no server serves can offer to connect one.
- A provider is served, and left out, while a connection made from it is pending, connected or broken, or while an entry of the configuration names it. That connection or that entry is in
servers, withproviderset and, while it is not ready, itsbecause. - A provider none of whose ways is offered is left out too, as while a value whoever runs Auto sets for it is not set.
list_providersstill lists it, each wayoffered: false. - Without
terms, it lists every such provider, sorted by name, while there are 100 or fewer, and none when there are more. - With
terms, it lists the providers whose name, title or description holds a word, scored as tools are, a word of the title counting as a word of the name. They come best first, those with the same score by name, at most 100. - It covers the whole org, whatever
playbooknarrows the servers to, and is left out withserver. - It is present exactly where the caller may add a connection, as
may_addoflist_providerssays: a signed-in person, or the local developer in local mode, withorg:write, on a runtime whoseconnections.people_may_addis notfalse. It is then empty when every provider listed is served or left out. A key, a caller who may only read and a runtime where nobody may add get noproviders. The playbook route never returns it.
introspect returns one tool in full, as its server lists it:
- its
description, cut to 4 KiB; - its
input_schema, the JSON Schema of the arguments it takes; - its
annotations, the hints its server gives it (readOnlyHint,destructiveHint,idempotentHintandopenWorldHint), left out when the server gives none.
A tool of an HTTP API takes one object: each path and query parameter by its name, and body for a JSON body. The schemas from the document's components that it refers to are placed under $defs.
GET /v1/orgs/acme/playbooks/sales/tool-servers/billing/tools/getInvoice
Authorization: Bearer <key>{
"server": "billing",
"tool": "getInvoice",
"description": "Reads one invoice.",
"input_schema": {
"type": "object",
"properties": { "id": { "type": "string" } },
"required": ["id"],
"additionalProperties": false
},
"annotations": { "readOnlyHint": true, "idempotentHint": true, "openWorldHint": true }
}validate checks arguments, at most 16 KiB as JSON, against the tool's input schema. It returns valid (true or false) and issues. Each issue has the JSON Pointer of the argument at fault as path, and what is wrong there as message. It lists at most 20 issues, then one saying how many more there are. For a schema the runtime's checker cannot handle, such as one with a regular expression, it returns valid: null with one issue saying why; the arguments may still be right. An argument out of a format the runtime checks, date, date-time, time, email, uri or uuid, is an issue at its pointer, such as Expected a string in the format date, such as 2026-04-01, and a format other than those, such as int64, is left unchecked while the rest of the schema is checked, so valid can be true.
POST /v1/orgs/acme/playbooks/sales/tool-servers/billing/tools/getInvoice/validate
Authorization: Bearer <key>
Content-Type: application/json
{ "arguments": { "id": 42 } }{ "valid": false, "issues": [{ "path": "/id", "message": "Expected string" }] }dry_run reports what execute would check for the tool and its arguments, without calling the tool:
| Field | Contents |
|---|---|
would_run |
Whether execute would call the tool now: the caller may, the playbook is not retired, the operator allows the tool, and its server responded |
needs |
playbook:write, the permission execute needs whatever the tool does |
caller_may |
Whether the caller has that permission |
playbook_retired |
Whether the playbook is retired, which execute refuses, since a retired playbook can no longer change |
allowed |
Whether the operator allows the tool on its server's entry, for runs and for execute alike |
read_only |
Whether the server marks the tool read-only; null when the tool's listing was not read, for a tool the operator does not allow or a server that could not be asked |
arguments |
The arguments checked as validate checks them, valid: null with one issue saying why when the tool's input schema was not read; null when none were given |
server |
reachable (yes or no) and, with no, the because field that says why the server could not be asked |
dry_run returns would_run: true even for arguments the tool's schema does not accept. execute does not check arguments, and the tool returns its own error for them. The arguments field reports the problem beforehand.
When a server or a tool cannot be used, the reads respond as below. Each refusal is unavailable, and no tool is called:
| Case | introspect and validate |
dry_run |
|---|---|---|
| A server not set up for the playbook | Kind tool_not_offered, because: "tool_server_not_configured", before any server is asked |
The same |
| A tool the operator does not allow | Kind tool_not_offered, because: "tool_not_allowed", before any server is asked |
Returns allowed: false |
| A tool the server does not list | Kind tool_not_offered, because: "tool_not_listed" |
The same |
| A connection that is not ready | Kind tool_not_offered, with its because, as below |
The same, or server.reachable: "no" with because: "connection_not_allowed" for an address its document names |
| A server that cannot be asked | Kind tool_server_failed, with its because, as execute returns it |
Returns server.reachable: "no", with its because |
execute calls one tool of one tool server with the arguments given, as a run of a reasoning function would call it. It returns what that run's model would see, and the document an interaction function's call reads. Use it to see what a tool returns before a function names it, or to find an id a prompt needs. Use it, too, to do what the person asked of the tool without first creating a function.
arguments is the object the tool's input_schema takes, {} when left out. It may be at most 16 KiB as JSON; larger arguments return invalid_input at /arguments before anything is called.
POST /v1/orgs/acme/playbooks/sales/tool-servers/billing/tools/getInvoice/execute
Authorization: Bearer <key>
Content-Type: application/json
{ "arguments": { "id": "inv-2041" } }{
"call_id": "0199b7e2-4c1d-7a3e-8f5b-6d2c1e0f9a8b",
"server": "billing",
"tool": "getInvoice",
"outcome": "result",
"text": "{\"id\":\"inv-2041\",\"total\":1200}",
"answer": { "id": "inv-2041", "total": 1200 },
"result_bytes": 98,
"duration_ms": 312,
"called_at": "2026-10-08T09:00:00.000Z"
}| Field | Contents |
|---|---|
call_id |
The id of the call, a UUID, under which its two events are recorded |
outcome |
How the call ended, from the table below |
text |
What a reasoning function's model would see, scrubbed of the server's secrets and cut at 64 KiB with a note. Text content is text; structured content is JSON only when there is no text; other content is a placeholder. For a failure, the words the model gets |
answer |
The document a call's read points into, scrubbed of the server's secrets: the structured content, else the first text block parsed as JSON, else that text. Absent when the tool returned neither, or when it is larger than 64 KiB as JSON |
result_bytes |
The size of the whole result as the playbook keeps it, scrubbed of the server's secrets, before the cut; null when there is none |
duration_ms |
How long the call took |
server_request_id |
The server's own id of the request, when the operator's request_id setting says where the server carries one |
called_at |
When the call was made, in ISO 8601 UTC |
outcome |
Means |
|---|---|
result |
The tool returned a result |
tool_error |
The tool returned an error or refused the arguments; a run's model may recover from this |
server_failure |
The server failed on the call |
timed_out |
The server did not respond within 30 seconds |
too_large |
The tool's result was larger than one call may return |
The response is 200 whenever the call was made, whatever the tool returned. Nothing is called when:
- the operator does not allow the tool, the server is not set up for the playbook, or the server does not list the tool:
unavailablewith kindtool_not_offeredandbecauseset totool_not_allowed,tool_server_not_configuredortool_not_listed, or for a connection that is not readyconnection_pending,connection_broken,connection_not_allowedorconnection_unavailable; - the server cannot be used:
unavailablewith kindtool_server_failed.
A call is live: a tool its server does not mark read-only may change something. That is why execute is marked destructive over MCP while a tool server is configured or people may add connections.
Each call is recorded in the playbook's history: as tool_call_started before it is sent, with connection_id beside server for a connection, and as tool_call_answered or tool_call_failed after. These are the same facts a run's tool calls record, with the caller who made the call as the one who acted.
- The call is recorded on a stream of its own, with no run.
list_runsandget_run_historynever show it, whilelist_playbook_eventsdoes. - The arguments and the result are kept unless the operator turns
record_contentoff for the server. - A caller that disconnects during the call leaves a start without an answer.
- When no run holds a connection to the server, each call opens one and closes it once the call ends.
A connection is a tool server that a person adds to the org through their agent, a provider by its name or a server given by hand, which every playbook of the org may use, or the playbooks its entry names. The agent adds it without its credential; the person approves it, and gives its credential where it takes one, on Studio's page, which its link opens. Let people add connections sets out the flow, the kinds of credential and the rules the operator sets. These routes are relative to /v1/orgs/{org}:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
add_connection |
POST /connections |
org:write |
provider and placeholders, or name, type, entry and credential for a server given by hand, credential left out for an MCP server, which says how it authenticates |
list_connections |
GET /connections |
org:read |
Optional include_removed |
get_connection |
GET /connections/{id} |
org:read |
Connection id in path |
reconnect_connection |
POST /connections/{id}/link |
org:write |
Connection id in path |
remove_connection |
DELETE /connections/{id} |
org:write |
Connection id in path |
list_connection_tools |
GET /connections/{id}/tools |
org:read |
Connection id in path; optional limit and cursor |
read_link |
GET /connections/{id}/link/{token} |
org:write, with the link's token |
Connection id and token in path |
approve |
POST /connections/{id}/approve |
org:write, with the link's token and the adder's session |
token |
give_credential |
POST /connections/{id}/credential |
org:write, with the link's token and the adder's session |
token, and value for a secret, or client_secret, or private_key with algorithm, for client_credentials, with client_id where the connection's credential holds none |
start |
POST /connections/{id}/consent |
org:write, with the link's token and the adder's session |
token |
finish |
POST /connections/{id}/consent/code |
org:write, with the link's token and the adder's session |
token, code, state, and iss where the authorization server sent one; or token, state and handle for a consent begun through Auto's consent service |
Every operation answers the connection: id, a UUIDv7; name; type, mcp or openapi; provider, the name of the provider it was added from, or null for a server given by hand; placeholders, the values given for the provider's placeholders, or null; entry; credential, how it is credentialled, never its value, and for a connection connected by consent the client it was made with; status, pending, connected, broken or removed; added_by, the caller who added it, by id and name; added_at; connected_at; connected_by, the caller who connected it, by approving it or giving its credential, by id and name, or null; asked_scopes, the scopes its credential asks, in their order, each { name, says }, says the sentence of the provider's way that says what the connection can do through the scope, as it stood when the connection was added, or null for a server given by hand, and empty where the credential asks none; granted_scope, for a connection connected by consent, the scope the server granted, which may be fewer scopes than its credential asked, as when the person unticks one on the provider's page, or null; granted_scopes, the scopes of granted_scope in the same shape, each with the words of the scope asked of that name and says null for a scope the server granted beyond those asked, such as offline_access, or null where granted_scope is; credential_ends_with, the last four characters of a secret or client secret of twelve characters or more, which the secret store keeps beside the value, or null; and broken, { kind, detail, at } while it is broken, else null: kind why, for a client to word, credential_refused where its server answered 401 to the credential it holds, credential_wanted where its server answered 401 to a connection of none, which holds no credential, asking for one, credential_gone where its credential is no longer in the secret store, credential_not_tokens where it is not the tokens of a consent, consent_ended where its consent was revoked or has ended, client_gone where the client it was made with is no longer registered, and key_unknown where Auto's consent service does not know its key; detail the runtime's sentence, which says what mends it; and at when the runtime recorded the break, in ISO 8601 UTC, so a page says since when in the person's own words. add_connection answers 201 with the connection and connect, { url, expires_at }: url is Studio's page connect under the Studio the person can reach, <the runtime's origin>/studio/connect?connection=<id>&token=<token> where the runtime serves Studio, as it does in local mode, since that page knows the runtime that serves it and asks it for the org, and https://studio.on.auto/connect?server=<the runtime's origin>&org=<org>&connection=<id>&token=<token> otherwise; the runtime's origin is PUBLIC_URL, or the server's own address in local mode, and the link expires 24 hours on. reconnect_connection answers 200 with the connection and connect alike, a new link that replaces any link before it, open or not. list_connections answers them newest first in connections, the removed ones only with include_removed. read_link answers the connection with link, { expires_at }, when its link expires, so the page can say how long the person has, consent, { started_at, expires_at }, while a consent is started and not finished, and, for a connection whose credential is authorization, client_source, the source of the client a start would take now, by the rule the start takes it by, auto where it would begin through Auto's consent service, left out where the runtime has no client the provider's way takes; approve, give_credential and finish, which use the link up, answer the connection alone.
- With
provider, the provider gives the server and how it is credentialled.nameis the provider's when left out;placeholdersgives the value of each placeholder the provider takes;credentialmay give itskindalone, one of the provider's ways, or itskindand thescopeof the way, aslist_providersanswers it, where the provider offers two ways of that kind, the kind alone taking the first of themlist_providersmarks offered, or the first of them where it marks none offered, and when it is left out the addition takes the first waylist_providersmarks offered, or the provider's first where it offers none, which the addition then refuses in its words; andentrymay giveplaybooks,allowedandrecord_contentalone.type, the server's fields ofentryand every field ofcredentialbutkindandscopeare refused, each at its field, since the provider gives them;credential.client_idis refused since the person gives the client id on the page. The connection'sentryandcredentialare the provider's with each value in its place, and every rule below applies to them as to a server given by hand. Withoutprovider,name,typeandentryare each required, andcredentialtoo for an HTTP API, since an MCP server says how it authenticates, andplaceholdersis refused. entryholds the server without its credential:urlformcp;documentandbase_urlforopenapi; andheaders,query_params,allowed,playbooks,record_contentandrequest_id, as an entry of the configuration file has them.urlanddocumentare each anhttpsURL, or anhttpURL on a loopback address.credentialis one object,kindand the fields that kind takes, each optional in the schema and refused asinvalid_inputat the field where its kind lacks it or does not take it:nonetakes no other field;secrettakesnameandin,header,queryorsecurity, sonameis the header, the query parameter or the security scheme of the document the value is sent as, and only anopenapiconnection takesqueryorsecurity;referencetakesvariable, a variable of the runtime such asCRM_API_KEYthat the operator lists inconnections.variables, whose value is sent where itsnameandinsay, as a secret's;client_credentialstakesissuer, anhttpsURL or anhttpURL on a loopback address, an optionalclient_idand an optionalscope; andauthorization, by which the person signs in at an authorization server and allows Auto, takesissuer, and as the provider gives themscope,parameters,authorization_endpoint,token_endpoint,revocation_endpoint,pkce,nonewhere the server of those endpoints takes no PKCE challenge, andclients, the client sources the provider's authorization server takes,registered,metadata_documentordynamic, and once connectedclient_idandclient_source, the client the connection was made with, whilescopestays the scope the way asked. An MCP server given by hand says how it authenticates: as the connection is added, and before anything is recorded, the runtime asks it once, with one request that carries no token, and reads the protected resource metadata its answer names, or its well-known paths publish where its answer names none. Where that metadata names an authorization server the runtime takes, the connection recordsauthorization, with that server as its issuer, the scope the server asks and the client the runtime makes for itself there,dynamicwhere the authorization server registers clients dynamically and elsemetadata_document, so the agent leavescredentialout, or givesauthorizationwith itskindalone, any other field beside the kind refused at its field;none,secretandreferenceare refused at/credential/kind, in words that name the authorization server and say to leave the credential out, whileclient_credentialsstand, since they take their token from an authorization server too. Where the server names no authorization server, the kind given stands, and acredentialleft out is refused at/credential, in words that say the server names no way of its own and ask how its credential is sent. An HTTP API given by hand, whose document names no authorization server, takes itscredential, and is refused the kindauthorizationat/credential/kind. Let people connect by consent says how. The same object is what every operation answers and what the org's record keeps.- A
secretor areferencesent in a header may name itsscheme, such asBearer, which the runtime writes, with a space, before the value it sends, so the person gives the token alone, the variable of a reference holds the token alone, and the server receivesAuthorization: Bearer <token>. Aschemeholds the characters of a header name and no$, and a credential that places no value in a header takes none.give_credentialrefuses a value that already starts with the scheme and a space, in any case, at/value. - The
client_idof client credentials is the org's, so the agent may leave it out and the person gives it on Studio's page with the client secret or the private key: a connection whose credential holds none answers its record without it,give_credentialtakesclient_id, and the record holds it from then on.give_credentialrefuses aclient_idat/client_idwhere the credential holds one, refuses one missing where it holds none, and refuses one that looks like a credential, since the record keeps the client id in the open and the secret or the key goes in its own field, where it is sealed.add_connectionrefuses aclient_idthat looks like a credential at/credential/client_id. get_connectionand the five routes of the link are served over HTTP alone, and no MCP endpoint serves them.get_connectionreads one connection of the org by its id; an agent sees whether the person approved a connection in thestatuslist_connectionsanswers for each. The three routes of the link are Studio's page's alone. Each takes the token, which the runtime keeps only as its SHA-256, and matches it to the open link of the connection: the newest one given, less than 24 hours ago, that nothing has used up.startandfinishconnect a connection whose credential isauthorization.startanswers201withauthorize,{ url, expires_at }, andclient_source, the source of the client it started with:urlis the page of the connection's authorization server where the person signs in and allows Auto, which carries the client, the return address, the pageconnect-returnof the Studio the connection's link opens, the Studio host's/connect-return,STUDIO_ORIGINfollowed by it,https://studio.on.auto/connect-returnunless it is set, or, where the runtime serves Studio,/studio/connect-returnon the runtime's own address, the scope, a state and a challenge, andexpires_atis ten minutes on, the time the consent has of its own; a second start replaces the first. The server sends the person back to the return address with a code, the state and its issuer, and Studio sends them tofinishwithtoken,code,stateandiss, which redeems the code, keeps the tokens sealed in the runtime's secret store and connects the connection.finishis bound to the start and not to the link's 24 hours, and refuses a state the consent was not started with at/state, an issuer that is not the server's at/iss, and a code the server refuses at/code, in Auto's own words; a refused code leaves the start open. The scope it records asgranted_scopeis the one the server granted, its scopes separated by single spaces, or the one asked where the server says none; a grant of no scope where one was asked, or of a scope that is not one, is refused, an empty grant where none was asked records none, and a finish refused once the tokens exist revokes them first. A connection connected by consent then serves its tools, and the runtime renews its tokens; Let people connect by consent says how. A start ofclient_sourceautobegan through Auto's consent service, with Auto's client at the vendor: itsurlis the page Auto's consent service made, whose return is the Studio host, which sends the person back to the Studio the link opened with a handle and the state, andfinishtakestoken,stateandhandle, takes the tokens from the service and connects the connection. A finish of the other shape than its start's is refused asinvalid_inputat the field it gave,/codeor/handle, saying which the consent answers, and a handle the service refuses at/handle; Consent through Auto says when a runtime starts one.approveconnects a connection whose credential isnoneorreferenceand uses its link up.give_credentialkeeps the value sealed in the runtime's secret store, connects the connection and uses its link up: giving the credential is its approval.- A connection is added by a person: on a runtime that signs people in,
add_connectionrefuses a key, and a runtime of API keys alone, which signs nobody in, refuses every connection, since no person could approve it. - On a runtime that signs people in,
approve,give_credential,startandfinishneed the session of the person who added the connection. In local mode the token alone binds. reconnect_connectiongives a new link to a connection that isbroken,connected, orpending, whether or not its link is still open, and changes nothing else of it; a removed connection is added again rather than reconnected. The new link replaces the one before, so only the newest works: the token of the link before is answerednot_foundbyread_link,approve,give_credential,startandfinish, in words that say so,There is no open link of this token: a newer link of the connection crm replaced it, so the page can send the person to the newest. A broken connection of areferencetakes no link: the runtime finds its variable set and answers itconnectedat once, withoutconnect, or refuses it withconflictwhile the variable is not set, since it reads its variables when it starts; a pending or connected one takes a link as any other kind does. On a runtime that signs people in, only the person who added the connection may reconnect it, as only they may use its link. Through the new linkread_link,approve,give_credential,startandfinishtake the connection, pending, connected or broken alike, and connecting it again replaces its credential: the runtime forgets the secret it held, and answers itconnected, withbrokennulland the newconnected_byandconnected_at. The tokens of a consent it held are revoked asremove_connectionrevokes them where the new consent took another client, and forgotten without a revocation where it took the same one, since some authorization servers end the person's whole grant to that client on one revocation. A client of the MCP tool that opens links is asked to open it, as foradd_connection, and its retry answerslink_ran_outwhere the link ran out unused, andlink_replacedwhere a newer link that is still open replaced it.remove_connectionremoves a connection for good, and the runtime forgets its credential, and what a consent started for it keeps, having first revoked at the provider the tokens of a connection connected by consent; tokens it cannot read or revoke now are logged and left to end on their own, and the removal goes on. Removing a removed connection succeeds and records nothing. Its name may then be given to a new connection.
| Case | Response |
|---|---|
An entry that holds a value that looks like a credential, a reference such as ${NAME}, or a field another entry would refuse |
invalid_input |
A provider the runtime does not have, at /provider: There is no provider named crm here; search lists the providers a person may connect |
invalid_input |
A placeholder the provider does not take, one missing, or a value its pattern or its choices refuse, that looks like a credential or that would move the address, at /placeholders/<name> |
invalid_input |
A credential.kind the provider does not offer, at /credential/kind |
invalid_input |
What the checks of an entry refuse in what the provider resolved, at /provider, or in a name the caller gave, at /name |
invalid_input |
| A kind of credential there is not, or a field its kind lacks or does not take | invalid_input |
A scheme beside a credential that is not sent in a header, or one that holds a $ or a character a header name does not |
invalid_input |
An MCP server given by hand that names an authorization server the runtime takes, with a credential of the kind none, secret or reference, at /credential/kind |
invalid_input |
An MCP server given by hand that names no authorization server, with its credential left out, at /credential, or of the kind authorization, at /credential/kind |
invalid_input |
An MCP server given by hand with authorization or its credential left out that names another resource or a malformed scope, or whose authorization server cannot be read, names another issuer, takes no S256 challenge or registers no client, at /credential/kind, and with none, secret or reference alike, but for another resource |
invalid_input |
An MCP server given by hand that does not answer, whatever its credential, or whose authorization server does not answer, unless its credential is client_credentials |
unavailable |
give_credential with a value that starts with the credential's scheme, a client_id where the credential holds one, none where it holds none, or one that looks like a credential |
invalid_input |
| A connection the operator's rules do not take: no one may add one, its host, its kind of credential or the variable its reference names | forbidden, with the sentence of the rule |
A name of an entry of tool_servers, or of a connection of the org that is not removed |
conflict |
| An org that holds 50 connections that are not removed, the most it may | conflict, kind connections_full |
| A token that is unknown, used up, expired or replaced by a newer link, or an id the org does not have | not_found |
add_connection or reconnect_connection by a key where people sign in, or by anyone on a runtime of API keys alone |
forbidden |
approve or give_credential with the session of another person, where people sign in |
forbidden |
start or finish of a connection whose credential is not authorization, a server that names a resource other than its own address, or an authorization server that names another issuer, takes no S256 challenge, publishes no metadata or takes none of Auto's clients |
conflict |
start or finish where a host of the authorization server is one the rules do not allow |
forbidden, with the sentence of the rule |
finish with a client no longer registered, or, once the tokens it got are revoked, tokens the runtime's secret store refuses, a grant of no scope where one was asked, or a grant that is not a scope |
conflict |
finish with no consent started, or one started more than ten minutes ago |
not_found |
finish with a state the consent was not started with, at /state, an issuer that is not the server's, at /iss, or a code the server refuses, at /code |
invalid_input |
start or finish where the authorization server does not answer |
unavailable |
give_credential, start or finish where the runtime's secret store cannot keep or read now what it needs, in the store's words; a finish whose code was redeemed then says the person starts again, its tokens revoked |
unavailable |
start whose client, registered by the runtime at the authorization server, is one its secret store refuses to keep, in the store's words |
conflict |
approve of a connection whose credential is to be given, or give_credential of one that takes none |
conflict |
reconnect_connection of a connection removed, or of a broken reference whose variable is not set |
conflict |
reconnect_connection by another person than the one who added the connection, where people sign in |
forbidden |
list_connection_tools answers the tools a connection's server offers now, those its entry's allowed names where it names any, so a page or an agent shows what one connection can do without searching the whole org. The runtime asks the server for its tools when it is called, as search does, and sends nothing to a tool. It answers connection, the id given, server, the connection's name, which a function writes before the slash, tools, sorted by name, has_more and next_cursor, read as Paging reads a list: limit from 1 to 100, 20 when left out, and cursor the next_cursor of the page before; a cursor no page gave is refused as invalid_input at /cursor. Each tool holds:
| Field | What it holds |
|---|---|
name |
The tool, as a function names it after the slash |
title |
What a person reads as its name, where its server gives one: an MCP tool's title, or the title of its annotations; an operation of an HTTP API has none, its summary opening its description |
description |
What it does, as its server describes it, whole up to 4 KiB, as introspect answers it; empty where it gives none |
read_only, destructive, idempotent, open_world |
The hints its server gives, the readOnlyHint, destructiveHint, idempotentHint and openWorldHint of the MCP specification, each left out where the server gives none, which the specification reads as a tool that may change something, may destroy what it changes, may not be repeated safely and reaches outside; an operation of an HTTP API is read_only for GET and HEAD, destructive for DELETE, idempotent for GET, HEAD, PUT and DELETE, and open_world always |
A connection that is not connected, or whose server cannot be reached, is refused as unavailable, with the kind, the because and the words a function's refusal and search give: tool_not_offered and the because A connection that is not ready lists, or tool_server_failed where its server fails. An id the org has no connection of, or one removed, is not_found.
A provider is a service Auto knows by its name, an MCP server or an HTTP API with how a credential reaches it, which a person connects by its name and the values its placeholders take. list_providers lists those a person may connect here, and search on the org route lists to a caller who may add a connection those that no server serves yet; Let people add connections lists those Auto ships, and whoever runs Auto adds providers of their own in its settings file, connections.providers. The route is relative to /v1/orgs/{org} and served over HTTP alone, for a page to read:
| Operation | Method and route | Permission | Input |
|---|---|---|---|
list_providers |
GET /providers |
org:read |
None |
It answers providers, sorted by name, may_add and refusal. A provider holds:
| Field | What it holds |
|---|---|
name |
Its name, the default name of a connection to it, given as provider to add_connection |
title |
What a person reads |
description |
What a function may do with it |
website |
Where a person reads about the server and makes a credential |
type |
mcp or openapi |
credentials |
The ways a credential reaches the server, the first the default: kind, secret, client_credentials, none or authorization; for secret, in and name, where the person's value is sent, and scheme; for client_credentials and authorization, issuer and scope; scopes, the scopes of scope in its order, each { name, says }, says one sentence of at most 120 characters that says what a connection can do through the scope, such as Read Gmail messages, threads, drafts, labels and settings., empty where the way asks none; a description for the person; offered, whether a person may connect it this way here, and because, why not, where it is not |
placeholders |
The values a person gives: name, description, pattern and example, or choices, each a value and a title, and required |
- A provider is listed where the operator's rules could allow it: a way is listed where
credential_kindstakes its kind and the hosts it reaches could passallowed_hosts, a host that holds a placeholder judged by its shape, a fixed host that is an address inside the network, such as10.0.0.5, passing only whereallowed_hostsnames it by itself, and a provider with no way left is not listed. Whether a connection is allowed is still checked when it is added and on every request it makes. - A way is
offered: falsewhile a value whoever runs Auto sets for it is not set, and a way of the kindauthorizationwhile Auto has none of the clients its provider's authorization server takes, as Let people connect by consent sets out, andbecausesays so:The provider drive takes consent through a client whoever runs Auto registers, which is not set up here, or, where a client is set up there but covers not every scope the way asks,The provider drive takes consent through a client whoever runs Auto registers, and no client set up here covers every scope its way asks, files.read files.write.add_connectionrefuses such a way at/credential/kindin the same words, or at/credential/scopewhere the caller named the way by itsscopeand the client set up covers not every scope it asks. Searching the tools says whatsearchdoes with a provider none of whose ways is offered. may_addsays whether this caller may add a connection here, andrefusalwhy not, in the sentence the first rule that refuses gives, in the orderadd_connectionrefuses: a caller withoutorg:write; a key where people sign in, or every caller where the runtime signs nobody in;connections.people_may_addset tofalse. It isnullwhere the caller may add. The providers are listed whatevermay_addsays.
A connected connection is a tool server of its org: search lists it with connection, its id, list_connection_tools lists its tools, a function names it by its name as name/tool, and introspect, validate, dry_run and execute reach it, as Tool servers describes. The runtime checks the operator's rules and the entry, as add_connection checks them, and reads its credential when it is first used after the runtime starts. A connection that is not ready is refused as unavailable of the kind tool_not_offered, and search lists it unavailable with the same because and words:
because |
The connection |
|---|---|
connection_pending |
Nobody has approved it yet; the person who added it opens its link, or reconnects it for a new link |
connection_broken |
Its server answered 401 as a listing or a call met it, its credential is gone from the secret store or is not the tokens of a consent, its consent was revoked or has ended, or its client is no longer registered; the runtime recorded it broken, with why and since when in broken, once, in words that say what puts it right, most often that the person reconnects it with reconnect_connection. A 403 naming insufficient_scope breaks nothing and fails that call alone, in words that name the scope the call lacks |
connection_not_allowed |
The runtime does not use it, in the words of why: a rule of the operator no longer takes it, in the sentence add_connection would give; connections.people_may_add is false; its entry is no longer one the runtime takes; the variable its reference names is not set; the configuration now sets up a server of its name; or a request it would make goes to a host the rules do not allow |
connection_unavailable |
Its credential cannot be read just now, in the words of why: the runtime's secret store cannot unwrap it or does not answer, which whoever runs the runtime puts right. The connection stays connected, and a later call reads the credential again |
A runtime without a key runs 100 free runs, then at most 15 a UTC day and 75 a UTC calendar month; with a key it runs on the credits of an org at Auto, starting each run itself against a license Auto signed and reporting what it started every hour. License a self-hosted runtime sets out how a key is had and the ways to activate it, and the self-hosted licensing reference how a licensed runtime runs. These routes are relative to /v1/orgs/{org}; the license covers the whole runtime, whatever org the route names.
| Operation | Method and route | Permission | Input |
|---|---|---|---|
get_license |
GET /license |
org:read or playbook:read |
None |
activate_license |
POST /license |
org:write |
key, the key Studio gave, alk.<org>.<runtime id>.<secret> |
activate_allocation |
POST /license/allocation |
org:write |
allocation, the offline credits an operator of Auto signed: payload, signature and kid |
report_allocation |
POST /license/allocation/report |
org:write |
Optional allocation, the id of the offline credits the report is of |
get_license answers where the runtime stands, to every member of an org, and is served over HTTP alone:
| Field | Value |
|---|---|
state |
unregistered while it holds no key; licensed while it runs on its license; out_of_touch from the license's warn_at while it has not reached Auto, still running on it; limited while it runs at the free rate though it holds a key; offline while it spends offline credits; invalid once they have stopped |
limited_because |
While it is limited: credits, cap, out_of_touch, key_rotated, key_revoked, org_closed or not_activated; null otherwise |
runtime_id |
The runtime's id, which an owner registers in Studio; a runtime that has no id yet takes one as it is first read |
free_runs |
{ used, of }: the first runs it used of its 100, while it has never held a license; null once it has |
org, plan |
The org at Auto and its plan, of the license of the key in force; null without one |
org_name |
The name of that org at Auto, as Auto signed it into the license, on one line of at most 80 characters; null without one, or where Auto named none or a blank one |
features |
The features of the license in force, such as prediction-onnx, which a model runtime that names one needs to do its work; [] without a license in force: without a key, before its key is activated, once the license lapsed at its limit_at while no live offline credits stand, once its key was replaced or removed or its org closed, or once offline credits it holds stopped |
standing |
Where the org's credits stood when Auto signed the license: ok, low, empty, overage, near_cap or capped; null without one |
licensed |
The runs it started on a license, in all |
runs_left |
The runs it may still start on its license; 0 while it is limited, null without a license |
credits |
{ available, owed, overage_cap } of the org when Auto signed the license; null without one |
issued_at, warn_at, limit_at |
When Auto signed the license, from when it is out of touch and from when it runs at the free rate, unless it reports first; null without one |
last_report_at |
When it last took a license from Auto, as it activated or reported; null without one |
fallback |
{ per_day, per_month, used_today, used_this_month, lifts_at } while it runs at the free rate, lifts_at being when it can run again once it has used the rate; null otherwise |
key_source |
Where the key in force came from: environment, configuration or activated; null without a key. A key in the environment or the configuration file wins at each start |
offline |
{ credits, spent, usable_until } while it holds offline credits; null otherwise |
invalid_because |
While it is invalid, the detail a run is refused in; null otherwise |
summary |
Where it stands in plain words, and how to put it right, as it says in its terminal |
activate_license answers 200 with the same document. The runtime keeps the key in its secret store, so it stands after a restart, unless AUTO_RUNTIME_LICENSE_KEY or the configuration file gives another, which wins at each start. A key that is not one of Studio is refused as invalid_input; one Auto does not know, rotated away, removed or for another runtime, and a license not signed by Auto, as license_invalid; a key of an org whose account is closed as account_closed; and an account service that cannot be reached as unavailable of the kind meter_unavailable. Activating the same key again takes a fresh license.
activate_allocation and report_allocation serve a runtime on offline credits, and are served over HTTP alone. activate_allocation answers 200 with allocation, its id, installation, the runtime's id, credits, spent, what the runtime admitted on them, and refresh_by, the date Auto signed into them, 30 days after they were issued or last refreshed, or 90 under an Enterprise order; offline credits not signed by Auto, for another runtime or past their date are refused as license_invalid, and what is not their JSON as invalid_input. Giving the same credits again changes nothing but a later refresh_by. report_allocation answers 200 with report, { allocation, installation, sequence, spent, at }, the next number and all spent so far, and signature, its HMAC-SHA256 under the runtime's key, the JSON an owner uploads in Studio; a runtime with no offline credits, with no key to sign with, or holding others than those named, is refused as conflict.
A successful operation responds with JSON and Cache-Control: no-store. The create operations, create_playbook and create_definition, return 201; other successful operations return 200.
An error is an RFC 9457 problem document, with Content-Type: application/problem+json. Inspect its reason and detail:
invalid_inputincludes anerrorslist of JSON pointers, and the kindinput_schemawhen the input schema of the function or workflow refused the input it was given, before it ran, orcursor_not_heldfor a cursor of changes the ledger does not hold.- A rejection that has a
kindcarries it. - An
unavailableorconflictrejection that has abecausecarries it. - A
conflictmay have the kindoversized, for a workflow whose output is larger than a run can record. - A refusal a person resolves somewhere carries
resolve_at, the absolute URL of that page, and a refusal of the runtime's license or meter carriesruntime_id, the runtime's id. A self-hosted runtime's refusal at the free rate also carrieslifts_at, when the limit lifts, and, where the runtime holds a key,limited_because, why it is limited to the free rate. The license refusals below say which carry what.
A refusal of the input of a function or a workflow says each issue in its detail, one sentence each, in the words of the input schema. A sentence names the field, then its description and examples, then the rule it broke. Each entry of errors holds one sentence and the field's pointer, so a reader can show it at its field:
{
"type": "https://on.auto/problems/invalid_input",
"title": "Invalid input",
"status": 422,
"detail": "month, The month to report on, such as \"April 2026\" or \"2026-04\": expected at least 6 characters.",
"reason": "invalid_input",
"errors": [
{
"detail": "month, The month to report on, such as \"April 2026\" or \"2026-04\": expected at least 6 characters.",
"pointer": "/input/month"
}
],
"kind": "input_schema"
}A nested field is named by its path, such as address, then city, and an item of a list by its place, such as lines, then item 2. A missing field is named at the field that holds it, such as The input: expected the field month. A workflow whose step refused its input is rejected with that step's errors, at the pointers the step saw.
| Status | Common reason |
|---|---|
| 400 | bad_request |
| 401 | Missing or invalid credentials |
| 403 | forbidden or origin_not_allowed |
| 404 | not_found |
| 405 | Unsupported method; see the Allow header |
| 409 | conflict or cancelled |
| 410 | unanswered |
| 411 | length_required |
| 413 | content_too_large |
| 415 | unsupported_media_type |
| 422 | invalid_input |
| 500 | internal |
| 503 | unavailable |
A problem document's type is a URI that names its kind of problem:
| Type | Status | Means |
|---|---|---|
https://on.auto/problems/bad_request |
400 | The request is malformed, such as a field given twice or a body that is not a JSON object |
https://on.auto/problems/unauthenticated |
401 | Credentials are missing or invalid |
https://on.auto/problems/forbidden |
403 | The credentials may not do that |
https://on.auto/problems/origin_not_allowed |
403 | A browser sent the request from an origin the server does not allow |
https://on.auto/problems/not_found |
404 | Something the request names does not exist |
https://on.auto/problems/method_not_allowed |
405 | The path does not take that method |
https://on.auto/problems/conflict |
409 | The request clashes with what is there |
https://on.auto/problems/tools_called |
409 | A run that may have called tools is not run again under its id; its reason is conflict. When every tool the run called only reads, it has because: "only_read", and its words say nothing was changed |
https://on.auto/problems/effect_unknown |
409 | A run called a tool its server does not mark read-only, and could not finish. Whether the tool changed something is not known. Its reason is conflict; a person, or a workflow rule that names it, decides what happens next |
https://on.auto/problems/credits_exhausted |
409 | The org's account has no credits left for the run; on a self-hosted runtime, one that runs at the free rate since its org has no credits or owes its cap and has used the rate, or one whose offline credits are spent; its reason is conflict. Nothing of the run is recorded, and it can be run again once the account has credits or the rate lifts |
https://on.auto/problems/license_required |
409 | The runtime holds no key and has used its free runs, the 100 it runs first and the 15 a UTC day or 75 a UTC month after; its reason is conflict. Nothing of the run is recorded, and it can be run again once the rate lifts or a key is activated |
https://on.auto/problems/license_invalid |
409 | The runtime runs at the free rate since its license can't be used right now, its key rotated or removed, its org closed, out of touch past its license's limit_at or not yet activated, and has used the rate; or its offline credits stopped; or a key given to activate is invalid; its reason is conflict. Nothing of the run is recorded |
https://on.auto/problems/account_closed |
409 | The account of the run's org is closed: its runs are refused until the org is restored, because: "org_deleted", which an owner can do only for a limited time, until the date the resolve_at page gives, after which it is purged and they never run again, because: "org_purged"; its reason is conflict, and nothing of the run is recorded |
https://on.auto/problems/cancelled |
409 | The run was cancelled, with the kind of its cancellation; its reason is cancelled |
https://on.auto/problems/unanswered |
410 | Nobody answered the request a run waited on, of an interaction function or a prediction function's build, with the kind expired or undelivered |
https://on.auto/problems/length_required |
411 | A body a route streams, a dataset or a candidate, was sent without its Content-Length |
https://on.auto/problems/content_too_large |
413 | The body is larger than the operation accepts: 1,048,576 bytes of JSON, or 64 MiB for a dataset or a candidate. Their Content-Length is refused before the body is read |
https://on.auto/problems/unsupported_media_type |
415 | The body is not sent as the media type the operation accepts, application/json in UTF-8 unless the route says otherwise, or is sent compressed |
https://on.auto/problems/invalid_input |
422 | The input does not fit, with the errors that point at it |
https://on.auto/problems/cursor_not_held |
422 | The since of list_org_changes or list_playbook_changes is not a cursor the ledger holds; its reason is invalid_input; ask again without since |
https://on.auto/problems/client_closed_request |
499 | The client disconnected before the response |
https://on.auto/problems/internal |
500 | Something went wrong inside the server |
https://on.auto/problems/unavailable |
503 | Something the server relies on cannot serve now |
https://on.auto/problems/tools_unfinished |
503 | A run called only tools their servers mark read-only, and could not finish; its reason is unavailable, and running it again under a new id is safe |
https://on.auto/problems/rebuilding |
503 | The view of a recall function is still being built; its reason is unavailable; try again later |
A run the runtime's meter refuses carries, beside its detail, where a person resolves it and which runtime it is, so a client can offer the way out rather than print the sentence. A problem a client does not know these members on still reads whole by its detail.
| Kind | resolve_at |
runtime_id |
because |
|---|---|---|---|
license_required |
https://studio.on.auto/licenses?installation={runtime_id}, where an owner gets the free key |
Always | free_daily_limit or free_month_used, the limit it reached |
license_invalid |
The same page of Studio, or https://studio.on.auto/licenses before the runtime has an id |
Once the runtime has an id | At the free rate, free_daily_limit or free_month_used; activating a key, key_rotated or installation_revoked, where Auto's account says so |
credits_exhausted |
Billing of the org in Studio at the free rate, the runtime's page offline, or where Auto's account says in Cloud | On a self-hosted runtime | At the free rate, free_daily_limit or free_month_used; offline, none_left; in Cloud, the case the account names |
account_closed |
The page Auto's account gives for restoring the org, which says until when it can be | Never | org_deleted while the org can still be restored, for a limited time, or org_purged, after which its runs never run again |
A refusal at the free rate, one whose because is free_daily_limit or free_month_used from a self-hosted runtime, also carries:
lifts_at, when the limit lifts, in ISO 8601 UTC: 00:00 UTC on the next day forfree_daily_limit, and on the first of the next month forfree_month_used, such as"2026-10-29T00:00:00.000Z";limited_because, oncredits_exhaustedandlicense_invalid, why a runtime that holds a key is limited to the free rate, asGET /v1/orgs/{org}/licensesays it:creditsorcaponcredits_exhausted, andout_of_touch,key_rotated,key_revoked,org_closedornot_activatedonlicense_invalid. A runtime without a key has none, and hosted Auto's refusals carry neither member.
The origin of Studio is the one STUDIO_ORIGIN names. Refusals gives the words of each.
When a tool-calling run cannot finish explains when a run ends as tools_unfinished or effect_unknown. Retrying under a run id says when a request returns tools_called.
Inside a workflow, three rejections of a run the workflow called differ from their HTTP form:
invalid_inputis status 422 over HTTP, but a workflow raises it as avalidationerror of status 400. Acatchfor it matches status 400 or thevalidationtype, never 422.- A cancellation of a called run that the workflow does not catch rejects the workflow as
invalid_input, not ascancelled, since the workflow itself was not cancelled. credits_exhausted,license_required,license_invalidandmeter_unavailable, when the meter would not start the run, reach the workflow only 24 hours on: until then its step waits and asks again (When the runtime will not start the run for now).
When the function's run does not succeed has the full table of what a workflow catch sees.
A 500 response contains an incident reference: its instance and the x-request-id response header identify the server log entry. Include that reference when reporting a problem, without sharing credentials or confidential input. A malformed HTTP request can get a bare status before the API handles it.
Retry-After comes only with an unavailable response that a retry of the same request may resolve. It never comes:
- with the kinds
tools_unfinished,tool_not_offeredormodel_not_offered; - with
becauseset tobuilds_of_the_day,stalled,not_finite,not_in_orderorno_model_runtime, which no wait resolves; - with
cancelled.
| Case | Response |
|---|---|
| A function names a model outside the deployment's allow list | unavailable with kind model_not_offered and because: "model_not_allowed", before the provider is called |
| A function names a model whose provider is missing | unavailable with kind model_not_offered and because: "provider_not_configured" |
| A function names a tool the playbook's servers do not offer | unavailable with kind tool_not_offered |
introspect, validate, dry_run or execute names a tool the playbook is not offered |
unavailable with kind tool_not_offered |
Use list_models to see the model references the server offers, and search to see the servers and tools a function may name.