← Back to Blog

OpenAI Responses API vs Chat Completions: Differences & Migration

Zivv••24 min read
Responses APIOpenAIMigration

For years, nearly every LLM application was built on /v1/chat/completions: a messages array goes in, a choices array comes out. It is simple, universal and supported by practically every SDK and tool. Then OpenAI introduced /v1/responses, positioned as the next-generation interface for reasoning models and agents, and its own tools such as Codex have moved over to it.

So what actually changed? Should you migrate an existing project? Where do migrations usually break? And what should you watch for when calling either API through a gateway like Zivv? This guide answers each in turn. If you are also moving from the official API to a compatible gateway, start with the OpenAI-compatible migration guide.

The Short Version

  • Chat Completions is stateless, simple and has the widest ecosystem support. It fits most chat and text-processing work and is not going away.
  • The Responses API is built around input items and output items. It natively represents reasoning, tool calls and multi-part output, and its streaming events are semantic. It is the better fit for agents and reasoning models.

For new projects — especially agentic ones that lean on tools and reasoning — start with Responses. If an existing Chat Completions integration works fine, there is no need to migrate for its own sake.

Request Shape

Here is the same task in both APIs. Chat Completions:

from openai import OpenAI

client = OpenAI(api_key="sk-your-zivv-key", base_url="https://zivv.pro/v1")

resp = client.chat.completions.create(
    model="gpt-5.5",
    messages=[
        {"role": "system", "content": "You are a concise technical assistant"},
        {"role": "user", "content": "Explain HTTP/2 multiplexing in three sentences"},
    ],
    max_tokens=300,
)
print(resp.choices[0].message.content)

Responses API:

resp = client.responses.create(
    model="gpt-5.5",
    instructions="You are a concise technical assistant",
    input="Explain HTTP/2 multiplexing in three sentences",
    max_output_tokens=300,
)
print(resp.output_text)

How the core fields map:

ConceptChat CompletionsResponses API
Endpoint/v1/chat/completions/v1/responses
Inputmessages arrayinput (string or array of items)
System promptsystem / developer messageinstructions (or a developer input item)
Outputchoices[0].messageoutput array (messages, reasoning, tool calls)
Output capmax_tokens / max_completion_tokensmax_output_tokens
Structured outputresponse_formattext.format
Reasoning effortreasoning_effortreasoning.effort
Usageprompt_tokens / completion_tokensinput_tokens / output_tokens
StateStateless, resend full historyOptional server-side storage and previous_response_id

The biggest mental shift: output is no longer a single message. One Responses call may return a reasoning item, then one or more function calls, then a message. The SDK's output_text is just a convenience that concatenates the text; agent code should iterate over output and handle each item by type.

State: previous_response_id

Chat Completions is purely stateless — on turn ten you resend turns one through nine. The Responses API gives you two options:

  1. Server-side state. Responses can be stored (controlled by store), and the next turn only needs previous_response_id plus the new input.
  2. Client-side state. Set store: false and append the previous output items to your next input yourself, which matches the Chat Completions mental model.
first = client.responses.create(model="gpt-5.5", input="Remember: the project codename is Atlas")
second = client.responses.create(
    model="gpt-5.5",
    previous_response_id=first.id,
    input="What is the project codename?",
)

A caveat that matters in production: how reliable previous_response_id is depends on the transport. In Zivv's WebSocket mode (what Codex uses for long sessions), the gateway remembers the previous turn's full input and output within the connection and expands previous_response_id into full context before calling upstream — it works even with store: false. After a reconnect the previous turn is gone and you get the standard previous_response_not_found error; just resend the full input. Over plain HTTP, it relies on upstream-stored data, which is not guaranteed when requests are scheduled across accounts, so manage context client-side there. Also note that server-side state does not make history free: prior tokens still count as input on every request. It saves bandwidth and code, not token spend. For that, see prompt caching explained.

Tool Calling

Function calling is where most migration edits land:

StepChat CompletionsResponses API
Tool definition{type: "function", function: {name, parameters}}{type: "function", name, parameters} (flat)
Model calls a toolmessage.tool_calls arrayfunction_call items in output
Returning resultstool-role message with tool_call_idfunction_call_output input item with call_id

Returning a tool result with Responses:

input_items = [{"role": "user", "content": "What's the weather in Beijing today?"}]
resp = client.responses.create(model="gpt-5.5", input=input_items, tools=tools, store=False)

for item in resp.output:
    if item.type == "function_call":
        result = get_weather(item.arguments)
        input_items.append(item)
        input_items.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": result,
        })

final = client.responses.create(model="gpt-5.5", input=input_items, tools=tools, store=False)
print(final.output_text)

OpenAI's Responses API also offers hosted tools such as web search and file search. Those depend on upstream managed services, so check the endpoint reference for what is available through the gateway. Your own function tools work regardless.

Streaming Events

Chat Completions streams a sequence of identically shaped chunks: you read deltas from choices[0].delta.content and stop at data: [DONE].

The Responses API streams semantic events, each with a type:

  • response.created — the response has started
  • response.output_item.added — a new output item (message, reasoning, tool call)
  • response.output_text.delta — a text increment
  • response.function_call_arguments.delta — a tool-argument increment
  • response.completed — done, with final usage
stream = client.responses.create(model="gpt-5.5", input="Write a short poem about gateways", stream=True)
for event in stream:
    if event.type == "response.output_text.delta":
        print(event.delta, end="", flush=True)
    elif event.type == "response.completed":
        print("\nusage:", event.response.usage)

The classic migration bug is code still looking for choices or waiting for [DONE]. Dispatch on event type instead; your client code usually gets cleaner in the process.

Why Codex Uses Responses over WebSocket

Codex is a textbook Responses API client. A single coding task can involve dozens of think, call tool, read result, think again loops. The item-based model expresses that loop naturally, and reasoning can carry across turns.

On top of that, recent Codex versions can connect to /v1/responses over WebSocket, sending turn after turn on one long-lived connection instead of opening a new HTTP request each time. The event content is essentially the same as HTTP SSE streaming. For tasks with many tool calls, the latency improvement is noticeable.

Zivv's /v1/responses supports both HTTP SSE and WebSocket mode, so once Codex points at https://zivv.pro/v1 nothing else is needed. See connecting Codex to an OpenAI-compatible gateway and the Codex docs.

Calling Both APIs Through Zivv

Both APIs share the same base URL and key. A quick check:

curl https://zivv.pro/v1/responses \
  -H "Authorization: Bearer sk-your-zivv-key" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-5.5","input":"ping","max_output_tokens":32}'

If the JSON comes back with an output array and usage, the path works. Confirm exact model IDs in the Model Hub or via GET /v1/models; support for each API can differ by model. For GPT models, prefer Responses; for native Claude features, use the Anthropic protocol at /v1/messages. Billing is group multiplier times the official model price per actual token, with top-ups at a flat 1 CNY = 1 USD.

Migration Checklist

  1. Switch the endpoint from /v1/chat/completions to /v1/responses.
  2. Replace messages with input and move the system message to instructions.
  3. Rename max_tokens to max_output_tokens and response_format to text.format.
  4. Flatten tool definitions and return results as function_call_output items.
  5. Parse by iterating over output instead of reading choices; use output_text for plain text.
  6. Handle streaming by event type and treat response.completed as the end.
  7. Update usage tracking to input_tokens and output_tokens.
  8. Pick a state strategy: WebSocket mode plus previous_response_id for long sessions; client-managed context for HTTP calls.

FAQ

Q: Is Chat Completions being deprecated? A: OpenAI has said it will keep supporting Chat Completions, and it remains the most widely compatible interface. New capabilities tend to land in Responses first, which is why new projects should lean that way.

Q: Is the Responses API more expensive? A: Both bill by input and output tokens at the same per-model price. Differences come from usage patterns: reasoning tokens count as output, and history still counts as input even with server-side state.

Q: Can I use previous_response_id through Zivv? A: Yes. In WebSocket mode the gateway honors it within the connection — even with store: false — and returns previous_response_not_found after a reconnect so the client can resend full input (Codex already does this). Over plain HTTP it depends on upstream storage and is not guaranteed, so manage context client-side.

Q: Can one streaming parser handle both APIs? A: No. Responses emits typed semantic events while Chat Completions emits uniform chunks, so you need separate parsing logic.

Q: Codex says the endpoint or model is not found. What now? A: Verify /v1/responses with the curl command above, then check that the base URL includes /v1 and the model name is exact. See fixing 404 model not found.

Q: Does Zivv offer embeddings? A: Not currently. Use another provider for embeddings and keep chat, reasoning and tool calls on Zivv.

Wrapping Up

Chat Completions is a chat interface; Responses is an agent interface. Both handle everyday conversational features, but once you are running multi-turn tool loops on reasoning models, the item-based model, semantic streaming and WebSocket mode make your code simpler and the experience faster. One Zivv key calls both — sign up and compare them with a single curl.