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:
jsconst 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:
jsconst 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 contract | Chat Completions | Responses API |
|---|---|---|
| HTTP endpoint | /v1/chat/completions | /v1/responses |
| Primary input | messages | input, plus optional instructions |
| Primary output | choices[].message | typed output[] Items |
| Multiple candidates | n can return multiple choices | one generation per response |
| Conversation continuation | resend message history | previous_response_id, Conversations, or manual Item replay |
| Structured output config | response_format | text.format |
| Streaming shape | chunks with choices[].delta | typed 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.
jsconst 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:
jsconst 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

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

A safe migration is incremental:
- Introduce an internal result type so business code no longer reaches directly into
choices[0]or assumesoutput[0]is text. - Shadow representative requests through both paths. Compare structured outcomes, refusals, truncation, tool intent, latency, and usage—not exact prose alone.
- Pick one state strategy and test recovery after a process restart. A response ID held only in memory is not durable conversation state.
- Migrate the complete tool round trip, including parallel calls and failure results.
- Replace the streaming dispatcher and test cancellation, disconnects, incomplete responses, and failures.
- 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.



