Migrate from Assistants API to Responses API
If you are looking to migrate-from-assistants-api-to-responses-api, this guide walks you through the conceptual shift, practical code changes, and operational steps needed to move your OpenAI‑based agents to the new Responses API before the Assistants sunset on 26 August 2026. The tone is friendly but authoritative, focusing on what you need to know to keep your application running smoothly.
Why the migration matters now
OpenAI has marked the Assistants API as deprecated, with a hard removal date of 26 August 2026. After that date, calls to /v1/assistants, /v1/threads, and related endpoints will return errors. Continuing to rely on the beta‑only Assistants stack risks sudden downtime, especially because many downstream libraries and tooling are already moving toward the Responses API. The good news is that the Responses API was designed as a superset of the old capabilities—offering built‑in tools, clearer state handling, and improved cost efficiency—so the migration is more about rethinking a few patterns than rewriting entire logic blocks.
Conceptual mapping: Assistants → Responses
Understanding the new mental model helps you avoid superficial copy‑paste errors. The official mapping is straightforward, but the behavioural implications are worth noting.
| Assistants API concept | Responses API equivalent | What changes for you |
|---|---|---|
| Assistant (persistent configuration) | Prompt (defined in the Dashboard) or inline instructions + tools per call | You no longer store an assistant object; instead you version your prompt template or pass instructions each request. |
| Thread (server‑side message history) | Conversation (via the Conversations API) or previous_response_id chaining | State is either kept on OpenAI’s side (Conversation) or carried explicitly via the previous response ID. |
| Run (asynchronous execution with polling) | Response (synchronous by default, streamable) | The polling loop disappears; you get the full result immediately or via a standard SSE stream. |
| Run steps (intermediate actions) | Items (messages, tool calls, tool outputs, reasoning traces) | You now work with a flat list of output items rather than nested step objects. |
The biggest shift is that state management moves from the platform to you, unless you opt‑in to the Conversations API for durable server‑side storage. This change affects how you think about continuity, debugging, and compliance.
Choosing a state strategy
You have three supported patterns for maintaining conversation context. Pick one per workload and document any exceptions; mixing patterns ad‑hoc leads to subtle bugs.
1. Client‑managed history (stateless server)
You keep the full message array in your own database and send it on every request.
const response = await client.responses.create({
model: "gpt-5",
instructions: SYSTEM_PROMPT,
input: conversationHistory, // array of {role, content}
tools: TOOLS,
store: false, // do not retain on OpenAI side
});
Pros: Full control, auditability, works for compliance‑bound or long‑lived chats. Cons: Slightly higher token cost because the entire history counts as input each turn.
2. previous_response_id chaining (lightweight server state)
Each response receives an ID; you pass that ID back on the next turn. OpenAI retains the chain for 30 days by default.
let priorId = null;
for each turn {
const response = await client.responses.create({
model: "gpt-5",
instructions: SYSTEM_PROMPT,
input: userMessage,
tools: TOOLS,
previous_response_id: priorId,
store: true, // keep server‑side for 30 days
});
// use response.output_text …
priorId = response.id;
}
Pros: Minimal code changes, low latency, no need to store full history. Cons: IDs expire after 30 days; you must implement a fallback to rebuild from your DB if the chain is missing.
3. Conversations API (durable server state)
Create a Conversation once, then attach it to each response. Items (messages, tool calls, outputs) persist until you delete the conversation.
const convo = await client.conversations.create({ metadata: { topic: "support" } });
const response = await client.responses.create({
model: "gpt-5",
instructions: SYSTEM_PROMPT,
input: userMessage,
tools: TOOLS,
conversation: convo.id,
});
Pros: Closest to the old Threads model; ideal for long‑running support bots or internal copilots. Cons: You now manage a separate object lifecycle and must consider retention policies (Conversation items are not subject to the 30‑day TTL that applies to plain responses).
Tip: For many applications a hybrid approach works well—use previous_response_id for everyday turns, but also write each input and output to your own store for replay, debugging, and compliance.
Tool migration: built‑ins and custom functions
All built‑in tools (web search, file search, code interpreter, computer use, MCP, deep research) remain available, but their configuration lives directly in the tools array per request.
Built‑in tools
File search: Instead of tool_resources on an assistant, you now pass vector_store_ids on the file_search tool.
tools: [{
type: "file_search",
vector_store_ids: ["vs_your_kb"],
max_num_results: 8,
}]
Web search: Enable with { type: "web_search_preview" }. The model decides when to invoke it.
Code interpreter: Requires the container configuration (usually { type: "auto" }).
tools: [{
type: "code_interpreter",
container: { type: "auto" }
}]
Custom functions
The schema is flatter: type: "function" sits beside name, description, and parameters. There is no nested function key.
{
type: "function",
name: "lookup_order",
description: "Retrieve order status",
parameters: {
type: "object",
properties: { orderId: { type: "string" } },
required: ["orderId"]
}
}
When the model requests a tool call, you receive an item of type function_call. You must execute your function and return a function_call_output item in the next request’s input array, while also passing the current response’s id as previous_response_id.
Parallel tool calls
Unlike Assistants, which processed tool calls sequentially by default, the Responses API expects you to handle multiple function_call items concurrently. If your tool implementation isn’t idempotent or hits a downstream rate limit, wrap the calls with a concurrency limiter.
const limit = pLimit(3); // max 3 concurrent
const outputs = await Promise.all(
toolCalls.map(call => limit(() => runTool(call)))
);
Streaming and event handling
Streaming is still Server‑Sent Events (SSE) when you set stream: true. The event names are flatter and more predictable.
| Old Assistants event | New Responses event |
|---|---|
thread.message.delta | response.output_text.delta |
run.step_details.tool_calls[0].agent | response.output_item.added (then check item.type) |
run.step_details.tool_calls[0].function.arguments.delta | response.function_call_arguments.delta |
Your consumer code should therefore listen for response.output_text.delta for text chunks and response.output_item.added/response.function_call_arguments.delta for tool activity. Remember to handle response.completed to know when the full response is ready.
Retrieval and vector stores
If your Assistants integration relied on file search, the migration path is straightforward but requires you to explicitly manage vector stores.
- Create a vector store (once) via
client.vectorStores.create(). - Upload files using
vectorStore.fileBatches.uploadAndPoll(). You can attach metadata (title, author, etc.) to each file for filtered search later. - Reference the store in each request:
tools: [{
type: "file_search",
vector_store_ids: [vectorStore.id],
include: ["file_search_call.results"] // optional, useful for debugging
}]
The include flag returns the raw chunks the model retrieved, which helps you build citation UIs or verify relevance.
Because file search tool calls are now metered separately ($2.50 per 1k calls as of early 2026), consider setting max_num_results to keep costs predictable.
Observability, testing, and parity validation
A migration that only swaps endpoints can hide regressions in state handling, tool ordering, or cost. Invest in a small parity harness before you shift traffic.
Build a replay harness
- Log a representative set of inputs (messages, tool outputs, metadata) from production Assistants traffic.
- Run those inputs through both the old and new paths in a staging environment.
- Compare:
- Final output text (semantic similarity, not just string equality)
- Tool call counts and sequences
- Latency and token usage
- Any error codes
A stronger model (e.g., GPT‑5) can be used as an automated judge to score semantic differences.
Instrumentation
Add OpenTelemetry spans (or your preferred tracing) around each responses.create call. Capture:
- model name
- token usage (
usagefield) - tool names and durations
previous_response_idpresence- conversation ID if using Conversations API
This data lets you spot cost spikes, latency regressions, or missing tool calls quickly.
Testing checklist
- Single‑turn queries with no tools
- Short multi‑turn conversations (2‑3 turns) with and without tool use
- Long multi‑turn flows that exceed the 30‑day server retention window (to verify your fallback logic)
- File search queries that rely on metadata filtering
- Custom function calls that return complex JSON
- Streaming scenarios, including client‑side disconnects
- Error paths: timeouts, invalid tool arguments, rate‑limit responses
Rollout plan: shadow, canary, cutover
Treat the migration as a reliability project, not a simple endpoint swap.
Phase 1: Shadow traffic (5‑10 %)
Deploy the Responses API path alongside the existing Assistants path, but do not serve its output to users. Compare logs and metrics. Look for divergences in tool call patterns, latency, or error rates.
Phase 2: Canary release (10‑25 %)
Route a small percentage of real user traffic to the new path. Feature‑flag the switch so you can roll back instantly if error rates or latency exceed your SLO budget.
Phase 3: Gradual expansion
Increase the canary in steps (e.g., 25 % → 50 % → 75 %) while monitoring the same metrics. Keep the rollback path tested and ready.
Phase 4: Full cutover
Once the canary runs clean for at least one full business cycle (ideally a week to capture weekly patterns), make the Responses API the default. Decommission the Assistants codepaths, remove any related SDK imports, and clean up stale secrets.
Phase 5: Post‑cutover verification
Continue to monitor for a few weeks. Run occasional parity checks on a sample of traffic to catch any drift that only appears under rare conditions (e.g., very long conversations, bursty tool use).
Common pitfalls and how to avoid them
| Pitfall | Why it happens | Fix |
|---|---|---|
Assuming previous_response_id works forever | Server‑side retention expires after 30 days | Implement a fallback that reconstructs history from your DB when you receive a 404 or not_found error. |
| Treating tool calls as serial | Responses API expects parallel handling by default | Add a concurrency limiter or make your tool idempotent; test with concurrent calls. |
Forgetting to send instructions every request | Instructions are not persisted like an Assistant | Centralise your prompt/template in a constant or config file and include it on each responses.create. |
Overlooking the flat function_call schema | Assistants used a nested function key | Update your tool definition objects; run your linting suite to catch the old shape. |
| Neglecting streaming event name changes | Event types dropped the thread. prefix | Update your SSE consumer to listen for response.* events; keep a unit test that validates the mapping. |
| Skipping retrieval parity | File search now requires explicit vector_store_ids and may return different chunk counts | Run side‑by‑side retrieval evals; compare relevance scores and adjust max_num_results or filters as needed. |
| Missing cost alerts for tool calls | File search and web search are now metered per call | Add alerts on tool‑call volume; set daily budgets if necessary. |
Assuming you can use both conversation and previous_response_id together | The API rejects the combination | Pick one primary state mechanism per workload; document any exceptions. |
Migration checklist
Before you declare‑done checklist
- [ ] Inventory: List all Assistants‑based endpoints, the assistants, threads, tools, and vector stores they use.
- [ ] Prompt conversion: Create Dashboard prompts from critical assistants or decide to inline instructions per request.
- [ ] State decision: Choose per‑workflow strategy (client‑managed,
previous_response_id, Conversations) and implement fallback for ID expiration. - [ ] Tool updates: Replace nested function schemas with flat shape, adjust built‑in tool configuration, add concurrency guards if needed.
- [ ] Streaming consumer: Rewrite event handlers to use the new
response.*names; test with both text and tool‑call streams. - [ ] Retrieval migration: Create vector stores, upload files with metadata, verify search relevance matches prior Assistants behavior.
- [ ] Observability: Add tracing for model calls, tool usage, latency, and cost; set up dashboards.
- [ ] Parity harness: Build a replay harness that compares Assistants and Responses outputs on a production‑like sample.
- [ ] Rollout: Execute shadow → canary → gradual rollout with feature flags and automated rollback criteria.
- [ ] Cleanup: Remove Assistants SDK imports, deprecated code, and any temporary compatibility shims.
- [ ] Documentation: Record the chosen state strategy, tool versioning, and rollback procedure for future reference.
Conclusion
Migrating from the Assistants API to the Responses API is less about learning a brand‑new API and more about embracing a cleaner, more explicit way to manage state, tools, and streaming. By treating the move as a phased reliability project—testing in shadow, validating with a parity harness, and rolling out with clear rollback gates—you can transition your agents well before the August 2026 deadline without sacrificing uptime or user experience.
The Responses API gives you finer‑grained control over conversation history, predictable pricing for tool usage, and a streamlined developer experience that aligns with modern agent patterns. Take the time to map your current Assistants usage, pick a state strategy that fits your compliance and operational needs, and invest in automated checks that catch regressions early. With those steps in place, your migration will be smooth, and you’ll be positioned to take advantage of upcoming OpenAI features that build on the Responses foundation.
