Open Responses: one contract instead of N×M adapters
A shared spec built on OpenAI's Responses API replaces a pile of provider adapters. The interesting part is not the field names but the state machine, the semantic events, and the MUST behind allowed_tools, truncation and previous_response_id — those decide who gets to make which call.
In one line: it swaps “messages” for bidirectional items, swaps token streaming for state-machine events, and gives extensions a namespaced side channel — so the endpoint and model name become configuration, and the cost moves from adapters to the semantics of individual fields.
Read from three places: the specification itself (both the 2026-01-15 and 2026-04-24 versions), its TypeSpec schema and repository, and the docs of two live implementations at Databricks and Vercel. Numbers are labelled by origin: counted in the repo, or read from the GitHub API.
1. What it actually defines#
Built on OpenAI’s Responses API, it fixes the shape of requests and responses for endpoints like /v1/responses. The scope is deliberately narrow: the API layer only. No model change, no retraining.
What it really defines is three things, not a pile of JSON fields:
| Piece | What the spec says | Why |
|---|---|---|
| Items | the atomic unit of context, bidirectional: input to the model, or output from it | a turn produces atomic events, not a string |
| Semantic events | streaming is a series of meaningful transitions, not text deltas | clients need to rebuild structure, not concatenate |
| Extensions | any implementer may be a proper superset; new types carry their own slug | providers need room to ship their own features without collisions |

It also defines compliance: an API is compliant if it implements the spec directly or is a proper superset of it. That sentence matters — you may add, you may not change what a standard field means.
2. Three parts: items, events, state machines#

Items carry four properties, each solving a specific problem:
- Polymorphic: discriminated by
type. Amessageand afunction_callhave different shapes; the client dispatches instead of guessing. - State machines:
in_progress/incomplete/completed. One hard rule: an item that endsincompletemust be the last item, and the containing response must also beincomplete. No “half sentence, then another one” ambiguity. - Streamable: the first event must be
response.output_item.added, carrying every field known at that moment (at leastrolefor a message, at leastnamefor a function call), the rest as zero values. The client can draw the skeleton before filling it. - Extensible: non-standard item types must be prefixed with the implementer slug, e.g.
openai:web_search_call.
Content is split asymmetrically into UserContent and ModelContent. User input may be text, images, audio, other modalities; model output is guaranteed text in the base protocol. The asymmetry is deliberate: a provider can later add output_image without over-generalising the user side.
Reasoning items are the most interesting detail: content (raw reasoning), encrypted_content (opaque, provider-specific), and summary (safe to show a user) are all optional. The spec does not require a provider to expose its chain of thought — only to be explicit about which of the three it hands you.
3. Four fields, four prices#

| Field | What the spec requires | Who pays |
|---|---|---|
previous_response_id | the server MUST load the prior input and output and treat the new request as input → output → new input, order preserved | the server keeps state; zero-retention deployments need another plan |
allowed_tools | the full tool list stays visible, but only the listed subset may actually run; servers MUST enforce it as a hard constraint | the server; the win is that prompt and schema caches survive |
truncation | with disabled, no truncation: over-long context must fail, not quietly shrink | the client, which must handle the error path |
service_tier | a hint about priority, multiple tiers allowed | the implementer maps it to queues and pricing |
allowed_tools is the best piece of design in the spec. The old way to narrow tool permissions was to edit the tools array — which invalidates every prompt and schema cache you had. Here the list stays and the executable set narrows, decoupling governance from cache economics.
4. Streaming and transport#
The SSE rules are few and hard: Content-Type: text/event-stream, each data object a JSON string, the event field must match the body’s type, and the stream must end on a terminal event (response.completed, response.failed or response.incomplete). [DONE] is optional and clients must accept its absence. Servers “SHOULD NOT” use SSE’s own id; ordering comes from sequence_number.
The 2026-04-24 version added WebSocket transport on the same /v1/responses resource, each turn opening with {"type":"response.create"} and using the same event objects. The limits are spelled out:
- one in-flight response per connection; parallelism means more connections;
- a 60-minute connection cap, then
websocket_connection_limit_reached; - continuation via
previous_response_id, which the server may hold in connection-local memory — which is exactly what makesstore=false(zero data retention) deployments workable; if it cannot find it,previous_response_not_found, never a silent downgrade; - a separate
/responses/compactreturns a compacted input window, not a response id; after compaction you dropprevious_response_idor set it to null.
Where state lives becomes a choice. In a zero-retention deployment the context travels with the connection, so dropping the connection loses it.
5. Extensions: a proper superset counts#
Three layers, increasingly permissive:
- Hosted tools: an implementer’s own search, retrieval or orchestration tool, named
implementor_slug:tool_name, executed inside the implementer’s environment. The spec requires a matching item type as a receipt — how the model invoked it, the status, the result — and that receipt must round-trip losslessly on a follow-up request. - Extended items and events: slug-prefixed, each event carrying at least
typeand a monotonicsequence_number, and clients must be able to ignore what they do not recognise. - Extended schemas: extra fields are allowed, but must not change the meaning of standard ones, and should be optional — the docs say plainly that this reduces portability.
Then there is the governance path: a feature that frontier models broadly adopt gets proposed to the TSC for standardisation. Spec, implementation and governance finally form one loop.
6. Where the ecosystem stands#
| Item | Value | Source |
|---|---|---|
| Spec versions | 2026-01-15, 2026-04-24 | the site’s version index |
| Licence / stars / forks | Apache-2.0 / 1,197 / 70 | GitHub API |
| Repository created | 2026-01-15 | GitHub API |
| Open issues | 47 | GitHub API |
| Languages (bytes) | TypeScript 269K, MDX 92K, Python 90K, TypeSpec 61K | GitHub languages API |
| Schema sources | 9 .tsp files (items, events, tools, responses, content, configuration, routes, helpers, main) | repo schema/ |
| Conformance | bin/compliance-test.ts and friends; a compliance page on the site | repo bin/ |
| Live implementations | Databricks Model Serving (Claude, Gemini, hosted open models), Vercel AI Gateway | their docs |
| Governance | a TSC under AAIF | spec site |
The spec is not a hand-written OpenAPI document but a typed source, written in TypeSpec, that generates clients and schemas. That keeps clients and prose from drifting; the price is a higher contribution bar — a field proposal means writing the DSL.
7. What actually changed, and what did not#
Changed:
- Adapters become configuration. Endpoint plus model name is a setting, not an
if (provider === …)branch; gateways (Vercel) and hosting platforms (Databricks) accept the same body and translate downward for you. - Evals and routing get a common floor: one request body against four vendors, with logs and latency aligned under one schema.
- Tool calls and streaming get a semantic layer: a call is an item, and execution authority is either yours (external tools) or explicitly the platform’s (hosted tools).
Unchanged:
- Compliant is not equivalent. The spec guarantees a request is understood and a stream reconstructable; it does not promise comparable reasoning, identical truncation policy, or the same hosted toolset.
- Extension fields do not travel. A client built on
acme:telemetry_chunkmust handle its absence elsewhere. - The MUSTs only matter if implementers mean them.
allowed_toolssays the server must enforce — whether it does is something you test.
8. Four checks you can run#
curla streaming request and see whether the last event is a terminal one, rather than the socket just closing.- Narrow
allowed_toolsto one tool, then steer the model at another. The server should reject or suppress it, not run it. - With
truncation: "disabled", send an over-long input. It should fail, not silently truncate. - Feed the client an unknown
acme:*event. It should ignore it and finish the standard stream intact.
9. Open questions#
- One response per WebSocket conflicts with the connection reuse and multiplexing that gateways like to do. For low-latency multi-turn agents, is it one socket per session, or a connection pool inside the gateway? The spec does not say.
- “A proper superset counts as compliant” gives implementers room, but the value of the phrase “Open Responses compatible” now depends on their discipline. Should there be a per-field compatibility matrix instead of a boolean?
References#
- Specification: https://www.openresponses.org/specification
- Repository: https://github.com/openresponses/openresponses
- Databricks Model Serving: https://docs.databricks.com/aws/en/machine-learning/model-serving/query-open-responses-models
- Vercel AI Gateway: https://vercel.com/docs/ai-gateway/sdks-and-apis/openresponses