Migrating from Chat Completions to the Responses API is not a one-line endpoint swap. The safe path is to preserve the model and prompt first, then change three contracts separately: the request shape, the typed response parser, and conversation-state ownership. After that baseline passes, migrate tools, structured output, and streaming one feature at a time.
This guide uses the official OpenAI API documentation as verified on 2026-07-24. It is a migration plan, not a claim that every application needs to move immediately. OpenAI’s current migration guide recommends Responses for new projects while stating that Chat Completions remains supported. Do not confuse this change with the Assistants API deprecation: OpenAI says Assistants was deprecated on August 26, 2025 and has a sunset date of August 26, 2026. Recheck the official deprecations page before scheduling production work.
1. Understand what changes conceptually
Chat Completions revolves around a list of messages. You send messages, receive choices, and usually read choices[0].message.content. Your application owns the transcript and resends the relevant messages on the next turn.
Responses uses a typed, item-based model. You send input, optionally put stable system-level guidance in instructions, and receive output, which may contain message, reasoning, function-call, or other item types. For plain text, official SDKs expose the convenience helper response.output_text. For anything more complex, inspect response.output instead of assuming that every entry contains displayable text.
That distinction matters because a successful HTTP response may include more than an assistant message. A parser that grabs the first output item can silently drop a tool call or mistake a reasoning item for user-facing text. The official migration guide also notes that Responses does not support Chat Completions’ n parameter for multiple candidate generations. If your product depends on n > 1, issue separate requests and preserve your existing selection logic.
The practical rule is simple: migrate the transport before changing behavior. Keep the same model identifier, prompt, temperature or reasoning settings where supported, and test corpus during the first comparison. Model upgrades and prompt rewrites belong in later experiments. Otherwise, you will not know whether a changed answer came from the endpoint, the model, or your own prompt.
2. Map requests and responses before changing features
Start with a non-streaming, single-turn text route. The following JavaScript examples use the same placeholder model so the endpoint contract is the only intended change.
Before: Chat Completions
import OpenAI from "openai";
const client = new OpenAI();
const completion = await client.chat.completions.create({
model: "YOUR_MODEL",
messages: [
{ role: "system", content: "Answer in one concise paragraph." },
{ role: "user", content: "Explain idempotency in API design." },
],
store: false,
});
const text = completion.choices[0].message.content;
After: Responses
import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "YOUR_MODEL",
instructions: "Answer in one concise paragraph.",
input: "Explain idempotency in API design.",
store: false,
});
const text = response.output_text;
For simple existing transcripts, OpenAI documents that a compatible array of role-and-content messages can be passed as input. That is useful for an incremental migration, but do not let compatibility hide the new output contract.
| Chat Completions | Responses | Migration action |
|---|---|---|
POST /v1/chat/completions | POST /v1/responses | Change the endpoint or SDK method |
messages | input plus optional instructions | Separate durable guidance from turn input |
choices[0].message.content | output_text for simple text | Use output for tools and typed content |
choices[].finish_reason | Response and item statuses | Rebuild completion checks |
usage.prompt_tokens | usage.input_tokens | Update telemetry field mapping |
usage.completion_tokens | usage.output_tokens | Compare like-for-like usage |
n | No direct equivalent | Make separate requests if candidates are required |
Do not ship the SDK helper as your only parser if the route may later use tools, citations, refusals, or multimodal output. Build a small adapter that returns your application’s own stable result type, such as { text, toolCalls, refusal, usage, providerRequestId }. That adapter becomes the seam for shadow testing and rollback.
If this change is part of a wider provider abstraction, the decision criteria in the existing OpenRouter alternatives guide can help separate an OpenAI-specific migration from a gateway migration. Combining both in one release makes failures harder to attribute.
3. Choose stateful or stateless conversation handling
Responses are stored by default. According to OpenAI’s conversation-state guide, response objects are retained for 30 days by default, and store: false disables that behavior. Conversation objects and the items attached to them are not subject to that same 30-day time-to-live. These are material data-lifecycle choices, not convenience flags.
Choose one state pattern per route:
- Application-managed state: Set
store: false, retain the input and relevant output items in your own system, trim them under your existing policy, and pass them into the nextinput. - Response chaining: Store responses and send
previous_response_idon the next request. OpenAI manages the earlier context in the chain. - Persistent Conversations API: Use a conversation object when several sessions, jobs, or devices must share durable context.
| State option | Best fit | Retention and cost point | Main migration risk |
|---|---|---|---|
Manual item replay with store: false | Existing application-owned transcripts, strict retention controls, or custom trimming | Your application stores the history; replayed context still consumes input tokens | Dropping typed output items needed by later turns |
previous_response_id | Simple threaded exchanges where OpenAI-managed chaining is acceptable | Response objects are stored by default; earlier input in the chain is still billed | Assuming prior top-level instructions carry forward |
| Conversations API | Durable context shared across sessions, jobs, or devices | Conversation items do not use the response object’s 30-day TTL | Letting provider retention outlive the application’s policy |
Do not mix these casually. When using previous_response_id, resend top-level instructions on every call because the previous response’s instructions do not automatically carry forward. OpenAI also states that prior input tokens in a response chain are still billed as input tokens; chaining is not a way to make historical context free.
For privacy-sensitive routes, make store: false explicit even if another environment currently supplies it through a wrapper. Record the state policy in code review and test it at the request boundary. Organizations operating under Zero Data Retention need a separate design for reasoning continuity; the official migration guide documents encrypted reasoning items for eligible stateless workflows. Do not infer eligibility from the SDK alone.
Before rollout, answer four questions: Who can retrieve stored responses? How are application deletions propagated? Which identifier links a user record to a response? What happens when a conversation outlives the application’s own retention window? If those answers are unclear, keep the first migration stateless.
4. Migrate tool calls and structured output as typed items
Function definitions and results change shape. In Responses, a custom function is a tool whose function name, description, parameters, and strictness are defined directly on the tool object. A returned call is a function_call item in response.output. After executing it, send a function_call_output item with the matching call_id. The current function-calling guide shows this linkage explicitly.
const first = await client.responses.create({
model: "YOUR_MODEL",
input: "What is the weather in Lisbon?",
tools: [{
type: "function",
name: "get_weather",
description: "Return current weather for a city.",
strict: true,
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false,
},
}],
store: false,
});
const call = first.output.find((item) => item.type === "function_call");
if (!call) throw new Error("Expected a function call");
const result = await getWeather(JSON.parse(call.arguments));
const second = await client.responses.create({
model: "YOUR_MODEL",
input: [
...first.output,
{
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(result),
},
],
store: false,
});
Treat tool execution as untrusted orchestration: validate parsed arguments, authorize the action independently, enforce timeouts, and make side effects idempotent. A correct schema does not grant permission. The existing guide to machine gates for AI output explains why deterministic validation and approval boundaries should sit outside model prose.
Structured Outputs also moves. A Chat Completions request that used response_format becomes a Responses request using text.format. The official Structured Outputs guide shows type: "json_schema", a schema name, strict: true, and the JSON Schema under text.format. Test refusals separately: a refusal can appear as a typed content item rather than an object matching your requested schema.
Do not migrate tools and structured output in the same commit unless your route cannot exercise them separately. First verify plain text, then schema-constrained text, then one read-only function, then functions with side effects behind the existing approval gate.
If the existing integration mixes JSON mode with schema validation, separate those requirements before changing endpoints. The internal JSON mode versus Structured Outputs guide covers that distinction. For recurring tool-call failures, use the tool-calling error guide to build negative tests instead of relying only on successful examples.
5. Rebuild streaming and error handling
Chat Completions stream consumers commonly concatenate choices[0].delta.content. Responses emits semantic events. OpenAI’s streaming guide lists common text events including response.created, repeated response.output_text.delta, response.completed, and error. Function arguments can arrive through events such as response.function_call_arguments.delta and response.function_call_arguments.done.
Update the stream state machine rather than renaming one field:
- Initialize display and audit state on
response.created. - Append only text delta events to visible text.
- Accumulate function arguments by item and call identity, not arrival order.
- Mark success only after the completion event and expected output validation.
- Preserve the response ID and provider request ID in logs.
- Treat a broken connection as indeterminate until retry policy and idempotency are evaluated.
Separate transport errors from model outcomes. HTTP authentication, rate-limit, timeout, and server errors belong in retry and operations logic. A refusal, an incomplete response, a tool-call validation failure, or schema-invalid content belongs in the application result model. OpenAI’s error-code guide is the source of truth for current API error categories; do not freeze a retry table from a blog post.
Retry only when the operation is safe. Text-only reads can usually use bounded exponential backoff with jitter. A tool that creates an order, sends a message, or changes a record needs an idempotency key and an application ledger before automatic retry. For a broader review of generated-code failure modes, use the existing AI-generated code review checklist.
6. Run a migration test plan with production-shaped cases
Build a fixed evaluation set from representative, redacted inputs. Include short and long prompts, multi-turn conversations, refusals, malformed tool arguments, parallel calls if used, structured-output edge cases, rate-limit simulation, and a stream interrupted mid-response.
For every case, compare:
| Dimension | What to record |
|---|---|
| Contract | Parsed text, typed items, schema validity, tool-call sequence |
| Behavior | Task acceptance criteria, refusal handling, factual checks |
| Operations | Latency distribution, retries, timeouts, incomplete responses |
| Usage | Input, output, cached, and reasoning tokens where available |
| State | Stored/not stored, chain continuity, deletion and expiry behavior |
| Safety | Authorization decisions, side-effect count, idempotency result |
Do not require byte-for-byte text equality. Require the application contract to pass. For extraction, that might mean schema validity and field-level correctness. For support answers, it might mean policy compliance, cited evidence, and escalation behavior. Keep model and prompt fixed during this stage, then assess any model upgrade separately.
Run the new adapter in shadow mode where policy permits: serve the Chat Completions result, call Responses on the same redacted input, and compare offline. Shadow calls can increase token spend and data processing, so cap the sample and verify retention settings first. Never duplicate side-effecting tool execution; mock tools or record proposed calls only.
Define promotion gates before seeing results. A reasonable project-specific gate might require zero duplicate side effects, no regression in schema-valid outputs, all privacy tests passing, and error/latency/cost changes inside budgets chosen by your team. Those thresholds are engineering decisions, not OpenAI guarantees.
7. Roll out gradually and keep rollback boring
Put endpoint selection behind a server-side feature flag at the adapter boundary. Start with internal traffic, then a small non-critical cohort, then expand only after a full observation window. Keep the old path deployable until the new route passes both normal traffic and failure injection.
Use this rollback checklist:
- The Chat Completions adapter still compiles and has a current smoke test.
- The feature flag can route new requests back without a deploy.
- In-flight Responses chains are not fed into the Chat Completions parser.
- Conversation records identify which API created each turn.
- Tool side effects use the same idempotency ledger on both paths.
- Dashboards distinguish API, model, route, status, token fields, and refusal outcomes.
- On-call documentation names the rollback owner and trigger.
- Stored-response and conversation cleanup procedures remain valid after rollback.
Rollback should stop new exposure, not erase evidence. Preserve redacted request IDs, adapter versions, failure classifications, and evaluation results long enough to diagnose the regression under your retention policy. Do not automatically replay failed side-effecting calls after switching endpoints.
Finally, schedule a post-migration review. Remove the old adapter only after the rollback window closes, production metrics remain within your declared limits, and all consumers have moved to the new normalized result type. Recheck OpenAI’s official migration, conversation-state, function-calling, structured-output, streaming, error, and deprecation documentation at that point. API details can change; a migration that was correct on 2026-07-24 should still be verified against the documentation on the day you release it.
Frequently asked questions
Is migration to the Responses API mandatory?
Not solely because Responses exists. As of the verification date, OpenAI says Chat Completions remains supported while recommending Responses for new projects. Treat model deprecations and the Assistants API sunset as separate timelines, and check the official deprecations page for the exact components you use.
Is the Responses API a drop-in replacement for Chat Completions?
No. The endpoint, request fields, output parser, state choices, function-call items, Structured Outputs location, and streaming events differ. A staged adapter migration is safer than changing all of those contracts in one release.
Does previous_response_id reduce the tokens billed for earlier context?
No. OpenAI states that previous input tokens in the response chain are still billed as input tokens. Use chaining to simplify state handling, not as an assumed token-cost optimization. For measured cost controls, see the internal LLM API cost calculator guide.
How do I prevent Responses from being stored by default?
Set store: false on each relevant request and verify the serialized request in a boundary test. This disables the default storage of response objects, but it does not automatically define retention for data copied into your own logs, traces, databases, or third-party observability systems.
What replaces n for multiple candidate responses?
Responses has no direct equivalent to Chat Completions’ n parameter. Make separate requests, then apply your existing ranking or selection logic. Budget and rate-limit those calls explicitly because each request has its own usage and operational failure surface.
Official sources
All sources below were checked on 2026-07-24:
- OpenAI: Migrate to the Responses API — request and output mapping, state options,
n, Structured Outputs, and rollout guidance. - OpenAI: Conversation state —
previous_response_id, default 30-day response retention, Conversations retention behavior, and token billing. - OpenAI: Function calling — typed
function_callitems andfunction_call_outputlinkage throughcall_id. - OpenAI: Streaming API responses — typed lifecycle and text-delta events.
- OpenAI: Deprecations — current shutdown dates, including the Assistants API sunset.