BYOKchat Blog

OpenAI Responses API vs Chat Completions: What’s the Difference?

A practical comparison of OpenAI's Responses and Chat Completions APIs, including state, reasoning, tools, streaming, portability, and migration tradeoffs.

· 9 min read

On this page
  1. The short comparison
  2. Chat Completions starts with messages
  3. Responses starts with a run made of items
  4. The biggest practical difference: who owns continuation state?
  5. Reasoning makes message-only abstractions harder
  6. Tool calls are another place the models diverge
  7. Streaming is not just “text arrives in pieces”
  8. Responses does not eliminate local conversation storage
  9. Why Chat Completions still matters for compatibility
  10. Do not migrate by renaming fields
  11. A practical migration sequence
  12. 1. Separate the provider adapter from the conversation model
  13. 2. Make history reconstruction deterministic
  14. 3. Add a Responses transport alongside Chat Completions
  15. 4. Test multi-turn reasoning and tools
  16. 5. Decide what you persist
  17. When I would choose Responses
  18. When I would keep Chat Completions
  19. The portability rule
  20. Where BYOKchat fits
  21. Further reading

OpenAI’s Responses API and Chat Completions API can both generate model output, stream text, and call tools, but they are not just two spellings of the same endpoint.

Chat Completions is message-centric. Responses is item-centric and designed to carry more of an agentic run as first-class protocol state. That difference becomes important once an application has reasoning items, built-in tools, multi-step tool calls, stored response state, or provider adapters.

If you are building a new OpenAI-specific agentic workflow, Responses is generally the more future-facing foundation. If you are building a portable chat client that must speak to many OpenAI-compatible servers, Chat Completions remains an important compatibility surface.

The useful question is therefore not simply “which API is newer?” It is:

Which state and capabilities should belong to the provider protocol, and which should belong to your application?

The short comparison

AreaResponses APIChat Completions API
Primary shapeTyped input/output itemsOrdered chat messages and choices
Conversation continuationCan reference prior response state or replay itemsUsually resend/manage message history yourself
Reasoning continuityDesigned to preserve reasoning-related items across turns where supportedMore limited representation of provider-native reasoning state
Built-in toolsFirst-class focusPrimarily function/custom tools and supported endpoint features
StreamingSemantic event stream with typed eventsIncremental completion chunks/deltas
InteroperabilityOpenAI-specific semanticsBroadly copied by OpenAI-compatible providers
Best fitOpenAI-native agents and advanced model featuresPortable chat integrations and simpler message workflows

Neither row means “good” or “bad.” It tells you where complexity lives.

Chat Completions starts with messages

A Chat Completions request is easy to understand because the application submits an ordered conversation:

{
  "model": "your-model",
  "messages": [
    { "role": "developer", "content": "Be concise." },
    { "role": "user", "content": "Explain TCP slow start." }
  ]
}

The response typically contains one or more choices, with the assistant message inside a choice.

This model has several architectural advantages:

  • the transcript is explicit;
  • persistence can be completely application-owned;
  • replaying a conversation is conceptually straightforward;
  • many non-OpenAI providers implement a similar endpoint;
  • adapters can normalize providers into a common messages -> assistant message workflow.

That simplicity is one reason /v1/chat/completions became a de facto compatibility dialect for local servers and third-party inference services.

But a model turn is no longer always just one assistant message.

A modern turn may include reasoning metadata, several tool calls, tool results, structured output, built-in server tools, intermediate items, and a final answer. Flattening every provider feature back into a message array can force the client to invent its own representation for state the provider already understands.

Responses starts with a run made of items

The Responses API uses a richer input/output model. A response can contain typed output items rather than treating the entire result as one assistant message.

Conceptually:

Response
├── reasoning item
├── function call item
├── function call item
└── message item

The exact item types depend on the model and enabled tools, but the architectural idea is the important part: a turn is a sequence of meaningful protocol objects.

That makes it easier for the API to represent workflows such as:

Diagram illustrating the surrounding section

A client can still present this as one normal chat turn. It simply no longer has to pretend that every intermediate object is a chat message.

The biggest practical difference: who owns continuation state?

For a basic stateless request, both APIs are simple. Long-running conversations make the distinction clearer.

With a classic message-based architecture, the application usually constructs the next request from its own stored history:

local conversation

serialize messages

POST /chat/completions

append assistant output

That gives the application strong control, but it also means it must correctly preserve any provider-specific state needed for future turns.

Responses can continue through previous_response_id when provider-side response state is available, or through replaying prior inputs and the required response output items when the application manages state itself. For stateless store: false or Zero Data Retention flows, OpenAI can return encrypted reasoning content so reasoning items can be round-tripped without exposing their internal contents.

The design choice is therefore not simply server state versus local state. Responses can be used in both styles:

  1. Provider-managed continuation — reference prior response state.
  2. Application-managed continuation — store and replay the required items yourself.

For privacy-sensitive or portable clients, application-managed continuation can be preferable, but it should be paired with the appropriate storage policy such as store: false or Zero Data Retention. Replaying items locally does not by itself disable provider-side response storage.

Reasoning makes message-only abstractions harder

Reasoning models expose why the richer item model exists.

Imagine a model performs hidden or opaque reasoning, calls a tool, receives a result, performs more reasoning, and produces text. The visible transcript may only show:

User: Find the likely cause of this deployment failure.
Assistant: The lockfile and runtime version are inconsistent...

But the provider may need additional reasoning state to continue efficiently on the next turn.

If a protocol can return that state as a typed item, a client can round-trip it without interpreting its contents. This is different from asking the client to expose chain-of-thought. An opaque or encrypted reasoning item can be treated as provider state rather than user-visible text.

That leads to an important implementation rule:

Persist provider continuation state because the protocol requires it, not because you want to render it as a chat message.

Your UI model and your transport model do not have to be identical.

Tool calls are another place the models diverge

Chat Completions has a mature function/tool-call workflow. A model returns tool calls, the application executes them, then sends tool results back using the identifiers from those calls.

Responses generalizes the run into typed items and is also the main home for OpenAI’s newer built-in agent capabilities.

For a provider-specific application, that can remove orchestration code. For a multi-provider client, however, you still need your own neutral tool model because Anthropic, Gemini, OpenAI, OpenRouter, and OpenAI-compatible servers do not expose exactly the same wire format.

A robust architecture therefore looks more like this:

Diagram illustrating the surrounding section

Do not make your entire application’s persistence schema equal to the JSON schema of one provider endpoint.

Streaming is not just “text arrives in pieces”

Both APIs stream, but the meaning of a chunk differs.

Chat Completions traditionally exposes completion chunks containing deltas. A client accumulates those deltas into the final assistant message and tool calls.

Responses emits a richer set of semantic events. Depending on the operation, an event can indicate that an output item was added, a text delta arrived, an item completed, a tool-related event occurred, or the whole response completed.

That difference matters because good streaming code should not be built as:

for await (const chunk of stream) {
  text += chunk.someText;
}

Instead, normalize transport events into application events:

type ChatStreamEvent =
  | { type: "textDelta"; text: string }
  | { type: "reasoningDelta"; text: string }
  | { type: "toolCallDelta"; callID: string; fragment: string }
  | { type: "usage"; inputTokens: number; outputTokens: number }
  | { type: "completed" }
  | { type: "failed"; error: Error };

The adapter is responsible for turning OpenAI’s wire events into these neutral events. The renderer should not know whether a text delta originated from Chat Completions, Responses, Anthropic Messages, or Gemini.

See How AI Streaming Works for the deeper transport model.

Responses does not eliminate local conversation storage

A common misunderstanding is that server-managed response state means a chat application no longer needs a database.

You still need local application state for things the provider does not own:

  • conversation title and organization;
  • drafts;
  • project membership;
  • attachment metadata;
  • local search indexes;
  • enabled tools and permission decisions;
  • provider/model selection;
  • UI state;
  • export and backup;
  • cross-provider migration.

A provider response identifier is a continuation handle. It is not a replacement for your product’s data model.

Why Chat Completions still matters for compatibility

Even when an OpenAI-native application prefers Responses, Chat Completions remains strategically important because many systems expose an OpenAI-compatible API.

Local model servers, gateways, inference hosts, and custom enterprise endpoints often advertise compatibility using endpoints such as:

GET  /v1/models
POST /v1/chat/completions
POST /v1/embeddings

Their compatibility may be partial, but the shared request shape is enough to make one generic adapter useful across many services.

Responses support outside OpenAI is less universal because its item types, built-in tools, state semantics, and newer reasoning behavior are a larger protocol surface to reproduce.

If you are designing a generic provider connection, “OpenAI-compatible” should therefore be treated as a capability profile rather than a promise that every OpenAI API exists. See What Is an OpenAI-Compatible API?.

Do not migrate by renaming fields

A weak migration strategy looks like this:

messages -> input
choices[0].message -> output_text

That may work for a trivial text request but misses the reason to use Responses.

A real migration should inventory behavior:

BehaviorQuestion to answer
Conversation stateWill you use provider-managed continuation or replay items?
ReasoningWhich returned items must be preserved across turns?
ToolsAre you using custom functions, built-in tools, or both?
StreamingCan your client handle typed events rather than only text deltas?
StorageWhich response objects belong in durable local state?
PrivacyAre server-side stored responses acceptable for this workflow?
PortabilityMust the same conversation switch to another provider?
ErrorsCan failures be mapped into the same application error model?

Only after answering those questions should you change the endpoint.

A practical migration sequence

For an existing application, migrate one boundary at a time.

1. Separate the provider adapter from the conversation model

If UI code directly consumes ChatCompletionChunk, fix that first. Define provider-neutral events and final turn objects.

2. Make history reconstruction deterministic

You should be able to take stored conversation state and produce exactly the request context intended for the next model turn.

This is useful regardless of API choice and makes fallback, export, and debugging much safer.

3. Add a Responses transport alongside Chat Completions

Do not delete the old path immediately. Run representative prompts through both implementations and compare:

  • final-answer quality;
  • tool correctness;
  • context usage;
  • latency;
  • token usage;
  • cancellation;
  • failure recovery.

4. Test multi-turn reasoning and tools

A “hello world” migration proves almost nothing. The difficult cases are continuations after reasoning and tool execution.

5. Decide what you persist

Avoid storing arbitrary provider JSON forever without a versioning strategy. Keep enough raw or typed provider state to continue correctly, but preserve your application-level transcript independently.

When I would choose Responses

Responses is the stronger default when:

  • the workflow is specifically built around OpenAI;
  • you need newer OpenAI-native agent capabilities;
  • reasoning continuity matters;
  • built-in tools reduce meaningful application complexity;
  • you are comfortable modeling typed response items;
  • you want the API to manage some continuation state.

When I would keep Chat Completions

Chat Completions remains reasonable when:

  • the integration is deliberately provider-neutral;
  • you target many OpenAI-compatible servers;
  • the workflow is mostly text plus ordinary function calling;
  • your application already owns conversation state cleanly;
  • the extra Responses semantics do not provide a measurable benefit.

The key is to choose intentionally. “Newer endpoint” is not an architecture.

The portability rule

If your app supports several providers, keep three layers separate:

product stateprovider staterendered transcript\text{product state} \neq \text{provider state} \neq \text{rendered transcript}

The product state is what your application needs to function. The provider state is what a particular API needs for correct continuation. The rendered transcript is what the user sees.

Collapsing those three into one data structure feels convenient until you add a second provider, tool protocol, or reasoning model.

Where BYOKchat fits

BYOKchat is designed around multiple provider connections rather than assuming one wire protocol defines the whole application. That makes the distinction above practical: a conversation can have a stable product-level representation while provider adapters handle the protocol-specific request, streaming, tool, and continuation details.

That separation is more important than choosing Responses or Chat Completions in isolation.

Further reading

Keep reading