AIFreeAPI Logo

Chat Completions vs Responses API: The Contract You Are Actually Changing

A
5 min readAI Development

Responses is the better starting point for most new OpenAI integrations, but a stable Chat Completions app does not need a panic rewrite. First identify the contracts your code assumes.

Chat Completions and Responses API contract comparison across input, output, state, tools, structured output, and streaming

For most new OpenAI integrations, start with the Responses API. For a stable application that only needs straightforward text generation, continuing to use Chat Completions can still be a sound engineering choice. OpenAI's current migration guide recommends Responses for new projects and, just as importantly, says Chat Completions remains supported.

The decision is not about a newer spelling for the same wire format. Chat Completions models a generation as messages in and choices out. Responses models messages, reasoning, tool calls, and tool results as typed Items. That shift affects every layer that assumes where text lives, how a turn continues, or what “done” means.

The smallest call hides the biggest difference

A one-turn text call makes the migration look almost trivial:

js
const completion = await client.chat.completions.create({ model: "gpt-5.6", messages: [{ role: "user", content: "Classify this support ticket." }], }); console.log(completion.choices[0].message.content);

The Responses equivalent is shorter:

js
const response = await client.responses.create({ model: "gpt-5.6", input: "Classify this support ticket.", }); console.log(response.output_text);

output_text is a useful SDK convenience for final text. It is not a replacement for the output array when your application needs tool calls, reasoning summaries, citations, or per-Item status. A production adapter should expose both a convenient final-text field and the underlying typed Items.

Application contractChat CompletionsResponses API
HTTP endpoint/v1/chat/completions/v1/responses
Primary inputmessagesinput, plus optional instructions
Primary outputchoices[].messagetyped output[] Items
Multiple candidatesn can return multiple choicesone generation per response
Conversation continuationresend message historyprevious_response_id, Conversations, or manual Item replay
Structured output configresponse_formattext.format
Streaming shapechunks with choices[].deltatyped semantic events

OpenAI documents that simple role/content arrays can be reused as Responses input. That compatibility is helpful during a text-only proof of concept; it does not make output parsing, tools, state, or streaming interchangeable.

State is a design choice, not a property of the chat UI

With Chat Completions, applications normally persist the relevant history and resend it in messages. Responses adds two managed options: chain a turn with previous_response_id, or attach responses to a Conversation. You can also remain stateless and replay the required Items yourself.

js
const first = await client.responses.create({ model: "gpt-5.6", instructions: "Act as a concise incident analyst.", input: "Group these alerts by likely root cause.", }); const followUp = await client.responses.create({ model: "gpt-5.6", previous_response_id: first.id, instructions: "Act as a concise incident analyst.", input: "Show only the group that needs immediate escalation.", });

Repeating instructions is intentional. The Responses create reference states that instructions from a previous response are not automatically carried into a request that uses previous_response_id. If a policy must remain active, your application still owns that policy.

State continuation and data storage also need separate decisions. OpenAI's current data controls describe retention for Responses, store, Zero Data Retention, background mode, prompt caching, hosted tools, and third-party services separately. The document currently says Responses application state may be retained for at least 30 days when stored by default or with store: true, subject to organization settings and documented exceptions. Do not turn store: false into a blanket privacy claim; trace the actual surfaces your workflow uses.

Tool calling changes the round-trip envelope

Both APIs can call application-defined functions, but they link the request and result differently.

Chat Completions places tool calls inside an assistant message. Your application appends a role: "tool" message whose tool_call_id matches the original call. Responses returns separate function_call Items. Your application sends back function_call_output Items linked by call_id:

js
const outputs = response.output .filter((item) => item.type === "function_call") .map((call) => ({ type: "function_call_output", call_id: call.call_id, output: JSON.stringify(execute(call.name, call.arguments)), })); const finalResponse = await client.responses.create({ model: "gpt-5.6", previous_response_id: response.id, input: outputs, });

The loop must handle every returned call, not only the first. A migration test needs cases for no call, one call, parallel calls, malformed arguments, a tool timeout, a tool error, and another tool call after the first results return. The official function-calling guide provides the current envelopes for both APIs.

Responses also exposes OpenAI-hosted tools such as web search, file search, code interpreter, and remote MCP through typed Items. That is a major reason to prefer Responses for agentic work. It does not mean every model supports every tool; model capability pages remain the authority.

Structured output and streaming are protocol changes too

Structured Outputs moves from response_format in Chat Completions to text.format in Responses. The JSON Schema itself may be reusable, but the request field, SDK types, refusal handling, and model support still need validation. OpenAI's Structured Outputs guide also distinguishes schema-constrained user output from function calling: use a structured text format when you want the model's answer shaped for your UI, and function calling when the model is invoking your system.

Streaming requires more than changing the expression that appends text. Chat Completions yields chunks whose choice contains a delta. Responses emits typed semantic events for text deltas, function-call arguments, output Items, and the overall response lifecycle. A robust event dispatcher should route each type to its own state machine and treat completed, failed, incomplete, and user cancellation as different terminal outcomes. The current streaming guide documents both stream shapes.

This matters for observability. An HTTP 200 or the arrival of one text delta does not prove the model response completed. Log the response ID, terminal status, emitted Item types, usage, and any error or incomplete reason.

Choose based on the workload you have

Workload choice matrix for Responses API versus Chat Completions with migration acceptance boundaries
Workload choice matrix for Responses API versus Chat Completions with migration acceptance boundaries

Responses is usually the better base when a new application needs reasoning continuity, hosted tools, multi-step function use, native multimodal input, long-running work, or an eventual agent loop. Its typed Items make those behaviors first-class rather than extensions attached to a single assistant message.

Staying on Chat Completions can be reasonable when an existing service is limited to single-turn text or simple structured output, is operationally mature, and gains no material capability from moving yet. “Supported but not the preferred greenfield primitive” is a more accurate description than either “identical” or “deprecated.”

Portability can also influence the decision. Many third-party providers implement the Chat Completions shape as a common denominator. An “OpenAI-compatible” label does not prove Responses parity. Verify endpoint support, Item types, streaming events, tool envelopes, state continuation, and error objects against that provider's current documentation.

Migrate behind an adapter, then prove the cutover

Six-step migration path from Chat Completions to Responses with contracts and cutover checks
Six-step migration path from Chat Completions to Responses with contracts and cutover checks

A safe migration is incremental:

  1. Introduce an internal result type so business code no longer reaches directly into choices[0] or assumes output[0] is text.
  2. Shadow representative requests through both paths. Compare structured outcomes, refusals, truncation, tool intent, latency, and usage—not exact prose alone.
  3. Pick one state strategy and test recovery after a process restart. A response ID held only in memory is not durable conversation state.
  4. Migrate the complete tool round trip, including parallel calls and failure results.
  5. Replace the streaming dispatcher and test cancellation, disconnects, incomplete responses, and failures.
  6. Recheck storage, redaction, metrics, and any third-party compatibility before shifting production traffic gradually.

The acceptance criterion is not “the sample returned an answer.” It is that every observable contract the old path supplied now has an explicit equivalent: conversations can resume, tools cannot disappear, schemas parse, streams close correctly, and failures are diagnosable. That is the point at which the endpoint change becomes a controlled protocol migration.