A new primitive, not just a new endpoint
For years every OpenAI integration spoke the same shape: POST a messages array to /v1/chat/completions, read choices[0].message. The Responses API (/v1/responses) is OpenAI's evolution of that — built for agentic workflows, with built-in tools (web search, file search, code interpreter, remote MCP) and server-side conversation state. Chat Completions isn't going away (OpenAI has committed to supporting it as an industry standard), but new agentic features land on Responses first, and the Assistants API is being sunset in favor of it.
If you're migrating, treat it as three concrete JSON changes: where you POST, how you read output, and how you carry state between turns.
Change 1: messages array → typed input/items
Chat Completions thinks in messages. Responses thinks in items — a union of types (messages, tool calls, tool outputs, reasoning) that better represents everything a model can do in a turn.
// Chat Completions request
{
"model": "gpt-5.2",
"messages": [
{ "role": "system", "content": "You are concise." },
{ "role": "user", "content": "Explain JSON Schema in one line." }
]
}// Responses request — note "input" instead of "messages"
{
"model": "gpt-5.2",
"instructions": "You are concise.",
"input": "Explain JSON Schema in one line."
}The system prompt becomes top-level instructions; input accepts either a plain string or an array of typed items when you need multi-turn or multimodal content.
Change 2: reading output from a typed array
Instead of choices[0].message.content, Responses returns a typed output array. The convenience field output_text aggregates the text for you, but the array is where tool calls and reasoning items live:
{
"id": "resp_01ABC...",
"output": [
{ "type": "message", "role": "assistant",
"content": [{ "type": "output_text", "text": "A JSON Schema describes the allowed shape of JSON data." }] }
],
"output_text": "A JSON Schema describes the allowed shape of JSON data."
}Change 3: structured outputs move to text.format
This is the one that trips people up. In Chat Completions your JSON Schema went in response_format. In Responses it moves to `text.format`:
{
"model": "gpt-5.2",
"input": "Extract the invoice fields.",
"text": {
"format": {
"type": "json_schema",
"name": "invoice",
"strict": true,
"schema": { "type": "object", "properties": { "total": { "type": "number" } }, "required": ["total"] }
}
}
}The schema itself is unchanged — same strict JSON Schema you already write. Only its location in the request moved.
Change 4: state via previous_response_id
Chat Completions is stateless: you resend the entire history every turn and pay for those tokens each time. Responses can store state server-side — pass previous_response_id to chain a turn onto the last one without resending the transcript. OpenAI reports meaningful cache-utilization gains from this (fewer repeated input tokens), at the cost of a small context-maintenance fee. You choose per app whether to keep managing history yourself or let the server hold it.
Migrating without breaking your parsers
Your downstream code cares about the response shape, so validate the new one before you flip traffic:
- Capture a real Responses payload and format it to see the
outputarray structure clearly. - Diff it against your old Chat Completions payload so every consumer that read
choices[0].messagegets updated tooutput/output_text. - Generate TypeScript types from the new shape so the compiler flags every call site that still expects the old fields.
Related reading: LLM JSON Mode & Structured Outputs and, for the Anthropic side, the Claude Messages API example.