Skip to content

Core wire behavior

The pinned OpenAI Python SDK (upstream.json) defines the /v1 routes, fields and types. This page states what Core does where those types are silent, such as status codes, error fields, defaults and list bounds, and where Core behaves differently from the official service. The coverage ledger lists the differences and open gaps; Sessions, events and history, message content, Vaults, source Files and Skills and Environment files and Artifacts own the rules of their resources.

"Beta routes" below are the routes under /v1/agents and /v1/vaults. "Files and Skills" are the routes under /v1/files and /v1/skills, which ignore OpenAI-Beta.

Requests ​

Paths and methods ​

CaseCore behavior
Empty, . or .. path segmentsServed on the canonical path, never redirected. Segments resolve with Go ServeMux semantics and a trailing slash is kept, so /v1/agents/x/../ reaches the trailing-slash 404.
Percent-encoded unreserved characters (A–Z, a–z, 0–9, -, ., _, ~)Decoded before routing, including %2E dot segments. Other escapes, such as %2F, %5C and double encodings, stay encoded and never separate segments. Every spelling reaches the route and authentication of its canonical path.
HEAD on a GET routeRuns the GET route after the same Beta and authentication checks and returns its headers without a body.
HEAD on the event stream, on File, Skill, Skill version and Artifact content, and on the Environment files list405, so HEAD never holds a stream open or reads content.
Unsupported method, including unknown methods such as FOO405, code unsupported_operation, message "This API method is not supported.", and an Allow header listing the route's methods in the order GET,HEAD,POST,DELETE.
Unknown sub-route under /v1, including a trailing slash404, code unsupported_operation, after the Beta and authentication checks.
OPTIONS and CORSNo CORS handling.

Creating an Agent, Vault, Credential, Environment Template, Environment file or Session returns 201, also for a streamed Session creation. An empty update body on an Agent or Environment Template advances updated_at and changes nothing else; timestamps have one-second precision.

Headers ​

Beta routes require exactly one OpenAI-Beta header value, equal to agents=v1. A missing, different or repeated value returns 400 with type and code invalid_beta and the message "To access the Agents API, set the 'OpenAI-Beta' header to 'agents=v1'." This check runs before authentication. agents=v0 is rejected.

Every Agents API response, including errors and event streams, carries:

HeaderValue
X-Request-IdA fresh req_ followed by 32 lowercase hex characters. Core also logs it as request_id. A caller-supplied value is not echoed.
OpenAI-Version2020-10-01
OpenAI-Processing-MsHandling time when the headers are written
X-Content-Type-Optionsnosniff
Cache-Controlno-store on JSON responses

Authentication ​

/v1 accepts only a Project API key as Authorization: Bearer <key>. All keys of one Project act as the same caller: they share its resources and its Session creation retries. Core resolves the key and its Project in the database on every request, with no credential cache and a five-second timeout. Revoking a key or archiving its Project takes effect on the next request. All keys of a Project act as subject service_account/project:<Project ID>. Projects and keys describes key management.

The optional OpenAI-Organization and OpenAI-Project headers must, when sent, appear once and equal core and proj_<Project ID>; any other value rejects the key.

FailureResponse
No key, another scheme, an empty or repeated Authorization header, an unknown or revoked key, a key of an archived Project, the Core key, or mismatched scope headers401, type invalid_request_error, message "A valid Agents API bearer key is required.", WWW-Authenticate: Bearer. The code is null on Beta routes. On Files and Skills it is invalid_api_key when exactly one Bearer credential was sent and rejected, and null otherwise.
The key lookup fails, for example because the database is unavailable503, type server_error, code authentication_unavailable

Request bodies ​

Every /v1 JSON route passes one body gate before route decoding, validation or lookup: Agent create and update, Vault create, Credential create and update, Environment Template create and update, Environment file create, Session create and update, and Session events. DELETE routes, multipart Files and Skills uploads and Skill update keep their own readers. Except for the 413 size error below, gate errors are 400 with type and code invalid_request_error and a null param.

OrderCaseResponse
1Content-Type missing, not JSON or malformed, including a bodyless POST. application/json and application/*+json are accepted case-insensitively, with parameters"expected request with Content-Type: application/json"
2Body over the route limit: 16 MiB for Session create and Environment Templates, the Environment file bound for file create, 1 MiB elsewhere413, code request_too_large
3Invalid UTF-8"Invalid body: encountered a unicode decode error when parsing this JSON value. Please check the value to ensure it is valid unicode."
4Malformed JSON, trailing data, two values, a byte order mark, a whitespace-only body, or an escape that forms a lone UTF-16 surrogate"Invalid body: failed to parse JSON value. Please check the value to ensure it is valid JSON. (Common errors include trailing commas, missing closing brackets, missing quotation marks, etc.)"
5A key repeated in one object, at any depth"Invalid body: duplicate JSON key '<key>' at '<path>'. Duplicate JSON keys are not supported." The path joins object keys with . and omits array indices, such as metadata.k or tools.type. Keys compare after unescaping and case-sensitively. The first repeat in document order is reported.
6A root that is not an object, including an array"Invalid type: expected an object, but got <kind> instead."
—An empty body or nullTreated as {}

Member names match exactly. A case variant such as Metadata or a nested Role is an unknown member and gets the route's unknown-member error before any write.

Errors guarded by echotext.Allowed, such as unknown-member, enum, schema-root and cursor errors, repeat caller values only when they are at most 256 bytes of printable UTF-8. An unknown member that cannot be repeated gets a generic message and a null param. Metadata errors use their own validation and may repeat longer keys.

Resource identifiers ​

A malformed path identifier gets exactly the response of a well-formed missing one on that route, including when the body or query is also invalid. Missing, malformed and foreign resources are indistinguishable. Identifiers that are UUIDs also resolve when written in another spelling that Go's UUID parser accepts, such as uppercase, braces or urn:uuid:.

Errors ​

Error types ​

Statustypecode
400 for a missing or invalid OpenAI-Betainvalid_betainvalid_beta
404 for a missing, malformed or foreign resource on Beta routesnot_found_errornot_found_error, message "Resource not found."
404 for a missing File or Skillinvalid_request_errornull
401invalid_request_errorSee Authentication
409conflict_errorconflict_error for conflicts the official service reports; Core-only conflicts keep their own code, such as idempotency_conflict
5xxserver_errorCore's code

Other 400 responses use type invalid_request_error. Validation failures with an official equivalent use code invalid_request_error and the observed param and message; other request errors keep Core's local codes, such as invalid_request or unsupported_or_invalid_configuration.

Validation errors ​

CaseResponse
More than 16 metadata pairs, a key over 64 characters or a value over 512 characters on Agent or Session create or updateParam metadata or metadata.<key> and the official message with the actual count or length. Pairs are checked before keys and values, keys in sorted order.
A non-string metadata value on Agent or Session create or update, or Vault createParam metadata.<key>, message "Invalid type for 'metadata.<key>': expected a string, but got <kind> instead." The first such value in document order is reported first.
Agent name over 128 charactersParam name. Empty and untrimmed names are accepted.
U+0000 in a metadata key or valueParam metadata.<key>
U+0000 or invalid UTF-8 in any other stored string or query filterNull param, message "Request text contains characters this service cannot store or compare, such as U+0000 or invalid UTF-8." Nothing is written. PostgreSQL cannot store U+0000, which the official service accepts.
Environment Template or inline Session network rejections: wildcards, ports, schemes, IPv6, empty hosts, a restricted policy without domains, more than 100 domains, domains with another access modeNull param, Core's message
Empty Session update body"At least one update field is required"

Lists ​

Lists return object: "list", data, has_more, first_id and last_id; an empty page has null first and last IDs. order defaults to desc. Environment files page with their own page token and are not covered here.

Query parameters ​

CaseBeta listsFilesSkills and Skill versions
Unknown key, including tenant_idIgnored, on lists and on single-resource routesIgnoredIgnored
Repeated supported key, including a scalar status400 invalid_request_error, null param, "Failed to deserialize query string: duplicate field <key>"400 unsupported_parameter400 duplicate_parameter, param <key>, the official message
order other than asc or desc, including an explicit empty order=400 invalid_request_error, null param, "Failed to deserialize query string: order: unknown variant <value>, expected asc or desc"400, null code, "order must be asc or desc."400 invalid_value, param order, "Invalid value: '<value>'. Supported values are: 'asc' and 'desc'."

The pinned Python SDK drops empty query values, so list(order="") sends no order and uses the default. after is trimmed of surrounding whitespace. Checks run in this order: repeated keys, then limit, then order; Vault and Credential status is checked first. These checks run before any resource lookup.

Vault and Credential lists accept status as a scalar, as status[] entries, or both, and filter by their union. Both statuses are listed by default. Another value returns 400 invalid_request_error with a null param and "Failed to deserialize query string: status: data did not match any variant of untagged enum VaultStatusFilterParam".

Page size ​

ListsDefaultAcceptedOther values
Agents, Sessions, Items, Environment Templates, Subagent Items, Subagent Turn Items201–1000 becomes 1; above 100 becomes 100
Vaults, Credentials201–1000, negative and larger integers, including overflowing ones, are clamped into 1–100
Turns, Subagents, Subagent Turns, Artifacts201–100400 invalid_request_error, "limit must be between 1 and 100"
Skills, Skill versions200–1000 returns an empty page whose has_more reports whether a resource follows the cursor. Negative: 400 integer_below_min_value, param limit. Above 100: 400 integer_above_max_value, param limit
Files100001–10000400 with a null code, "limit must be between 1 and 10000."

A limit that is not a decimal integer, including an empty value, returns 400 invalid_request_error, "Failed to deserialize query string: limit: invalid digit found in string" on Beta lists; outside Vault and Credential lists, a value above the signed 64-bit range returns "Failed to deserialize query string: limit: number too large to fit in target type". A leading + is accepted when encoded as %2B; a leading - returns the invalid-digit error on Beta lists except Vaults and Credentials. Skills return invalid_request, "limit must be an integer between 0 and 100."; Files return invalid_request with the Files range message.

Cursors ​

after names a resource of the same list, inside its already resolved parent and tenant. The parent is resolved first: a missing or foreign parent returns its 404 before the cursor is read. A cursor that does not resolve, whether random, malformed, of another type, of another parent, deleted or of another tenant, returns:

ListsResponse
Agents, Sessions, Turns, Environment Templates, Vaults, Credentials404, type and code not_found_error, "Resource not found."
Session Items, Subagent Items, Subagent Turn Items400 invalid_request_error, null param, "Invalid session item ID in after"
Subagents, Subagent Turns400 invalid_request_error, null param, "Invalid resource ID in after"
Session Artifacts400 invalid_request_error, null param, "after is not a valid artifact ID"
Skill versionsA value that does not begin with skillver: 400 invalid_value, param after, "Invalid 'after': '<value>'. Expected an ID that begins with 'skillver'." A version of another Skill: the same fields, "Skill version cursor does not match this skill." A malformed skillver suffix or a missing, deleted or foreign version: 404 with a null code and param
Skills404 with a null code and param
Files404, param after

Agents ​

Saved configuration ​

Agent create requires model. Core saves and returns these values for omitted fields:

FieldSaved value
name, instructionsnull
metadata{}
tools[]
text{"format": {"type": "text"}, "verbosity": "medium"}
reasoningSaved as sent; an omitted effort stays unset rather than taking a model default. Agent and Session responses always carry reasoning.effort and reasoning.summary, null when unset
service_tierauto
multi_agentDisabled. When enabled without max_concurrent_subagents, 6
Function defer_loadingfalse
programmatic_tool_calling.enabledtrue
web_searchEvery pinned mode is saved; see tool policy
HTTP MCP transportSaved with headers: {}; nonempty headers are rejected. Origin and allowlist defaults are in public MCP connection origin

Saving a value does not make it executable. Session creation admits a smaller set; see Session admission.

Configuration validation ​

Agent create and update bodies and the inline agent of Session create are checked against the pinned shapes of tools, text, reasoning, service_tier, multi_agent, model, name and instructions, before their parsers and before Harness admission. Failures return 400 with type and code invalid_request_error:

CaseParamMessage
Missing required memberJSON path, such as tools[0].parameters; on Session create agent.tools[0].parametersMissing required parameter: '<path>'.
Unknown member, including a case variant and the unpinned tool_choiceJSON pathUnknown parameter: '<path>'.
Wrong JSON typeJSON pathInvalid type for '<path>': expected <kind>, but got <kind> instead.
Unsupported enum valueJSON pathInvalid value: '<value>'. Supported values are: ... with the pinned values
Integer below minimumJSON pathInvalid '<path>': integer below minimum value. Expected a value >= 1, but got <n> instead.
Repeated function name, more than one web_search, more than one tool_searchnullduplicate function tool name: <name>, duplicate web_search tool, duplicate tool_search tool
Function parameters with a string root type other than objectnullInvalid schema for function '<name>': schema must be a JSON Schema of 'type: "object"', got 'type: "<type>"'.
text.format JSON schema with a string root type other than objectnullagent.text.format.schema must have top-level type "object"; got "<type>", also on Agent requests

Within one object Core reports a union's type first, then unknown members, then member values in document order, then missing members; tools before text, and the whole object before the duplicate and schema-root checks. Schemas without a string root type are not checked. Function and output schemas, MCP transport, request_metadata, metadata and x_agents_core keep their own parsers. Update bodies and the inline Session agent are validated before the Agent lookup, so owned, foreign, missing and malformed Agent IDs give the same response.

Core saves values the pinned shapes allow even when it cannot run them: function names of any length, enabled programmatic tool calling, reasoning effort max and service tier flex.

Session admission ​

A Session's effective configuration must also pass execution admission, which applies to saved and inline configuration alike. Admission reports protocol errors from the table above first, including duplicate tools and schema roots in saved Agents, then these, all 400 unsupported_or_invalid_configuration before any write:

ConfigurationMessage
Explicit reasoning.effort or reasoning.summary"Explicit reasoning execution options are not supported by this service yet."
service_tier other than auto"Execution currently supports service_tier=auto only."
Enabled or omitted-mode web_search, enabled programmatic_tool_callingSee tool policy
More than 64 functions, or a function name that is blank or longer than 512 bytes"This service supports at most 64 function tools." or "Function names must be nonempty, unique and at most 512 bytes."
Two programmatic_tool_calling declarations, two MCP servers with one label"Execution requires distinct tool controls.", "Execution requires distinct MCP server labels."

A per-Session tools replacement admits a Session whose saved tools would be rejected. Support for each tool and Harness is in execution and tools.

Omitted, null and explicit medium text verbosity give the same Session configuration. For a model whose native catalog declares no verbosity support, the Codex adapter drops a medium setting and uses the model's default, and rejects low or high.

Update, delete and list ​

OperationCore behavior
POST /agents/{agent_id}Replaces only the supplied fields. Nested objects replace the whole field; null name or instructions clears it; null or {} metadata clears all pairs, and an object replaces them. Existing Sessions keep their snapshots.
DELETE /agents/{agent_id}Returns {id, object: "agent.deleted", deleted: true}. Sessions created from the Agent, their history and their creation retries are unaffected. A repeated or missing deletion returns 404, and new Sessions that name the Agent return 404.
GET /agentsPages by creation time, then ID.

Sessions ​

Configuration snapshot ​

Session creation copies the effective Agent configuration into an immutable snapshot. With agent_id, the saved Agent is read once; fields in the inline agent replace the saved field whole, including arrays, and null tools clears the list. Omitted fields inherit; an inline x_agents_core that omits harness keeps the saved harness. Saved Agent metadata never becomes Session metadata. Later Agent updates or deletion affect only new Sessions.

stream defaults to false. stream and agent_id cannot be null. Omitted or null metadata is {}.

Creation validates the body and metadata types, the request fields and initial input, and the placement and streaming input requirements before looking up a creation retry. For new work, Core resolves the Template, saved Agent and model configuration, binds Vault Credentials, then validates the selected Harness and execution configuration before writing. A failed dependency lookup rechecks the retry identity so an already committed creation remains recoverable.

Creation retries ​

Send an Idempotency-Key of 1–128 bytes that is not only whitespace; a longer or whitespace-only key returns 400 invalid_request. An empty header counts as no key. Without a key, every request creates a new Session. The official service creates a new Session for each request even with the same key; Core returns the original one.

CaseResponse
Same key, same request, same Project201 with the Session's current state. No input is admitted again. A stream=true retry returns 201 with no events and closes.
Same key, different request409 idempotency_conflict
Same key after the Session was deleted409 idempotency_conflict

Keys are scoped to the Project; any key of the Project, including one issued after a rotation, can retry. A request that has an inline Agent without model or names a saved Agent, a template, initial files or preparation, vault_ids or credential references, x_agents_core, or an openai_hosted environment is compared as sent, before any of those sources is read: a matching retry returns the original Session even after the Agent, template, Credential or deployment default changes or is deleted. Other requests are compared by their resolved configuration. Model provider keys enter the comparison only as fingerprints.

Update and list ​

POST /agents/sessions/{session_id} accepts only metadata, which is required: null or {} clears it and an object replaces all pairs. Execution state and the creation retry identity are unchanged.

GET /agents/sessions accepts agent_id, which matches the Session's immutable root Agent ID, including inline Agent IDs and Agents that were since updated or deleted. The filter applies before pagination; an empty agent_id is a filter, not an omission.

Delete ​

DELETE /agents/sessions/{session_id} deletes a Session that is idle or failed, has no queued, running or waiting root Turn and no pending input reservation.

CaseResponse
Deletable200 {id, object: "agent.session.deleted", deleted: true}. Reads, updates, input, Turns and Items of the Session then return 404, and its open event streams end. Core releases the Session's Core-managed sandbox; a self_hosted machine and its files are left alone.
A root Turn is queued, in progress or waiting for required actions, or input is waiting for admission, a self-hosted connection or hosted provisioning409, type and code conflict_error, null param, "session must be durably idle or failed without required actions before deletion". Nothing changes.
The caller's own Session, already deleted200 with the same confirmation
Missing, malformed or foreign404

Subagent child Turns and pending Environment file writes do not block deletion. To delete running work, send agent.session.input.cancel, wait until the Session is idle, then delete. Input waiting for its Environment cannot be cancelled; the Session becomes deletable when the input starts, its five-minute deadline passes or the Environment fails. Core admits a Turn in the same transaction that returns 202 for its input, so a deletion right after that 202 returns 409.

Response fields ​

A Session's agent.tools omits tool_search declarations, which the pinned Session tool union does not include; the frozen configuration keeps them. On self_hosted Sessions, environment.remote_url is Core's daemon WebSocket URL, /api/v1/agent-daemon/ws under the public URL, which only OpenAgentCore's Runtime daemon speaks; requests cannot set it. Other Session fields follow the pinned types; x_agents_core is described in the Agents API guide.

Released under the MIT License.