Migrate from Assistants API to Responses API

OpenAI
Assistants API
Responses API
migration
AI development

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 conceptResponses API equivalentWhat changes for you
Assistant (persistent configuration)Prompt (defined in the Dashboard) or inline instructions + tools per callYou 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 chainingState 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 eventNew Responses event
thread.message.deltaresponse.output_text.delta
run.step_details.tool_calls[0].agentresponse.output_item.added (then check item.type)
run.step_details.tool_calls[0].function.arguments.deltaresponse.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.

  1. Create a vector store (once) via client.vectorStores.create().
  2. Upload files using vectorStore.fileBatches.uploadAndPoll(). You can attach metadata (title, author, etc.) to each file for filtered search later.
  3. 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 (usage field)
  • tool names and durations
  • previous_response_id presence
  • 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

PitfallWhy it happensFix
Assuming previous_response_id works foreverServer‑side retention expires after 30 daysImplement a fallback that reconstructs history from your DB when you receive a 404 or not_found error.
Treating tool calls as serialResponses API expects parallel handling by defaultAdd a concurrency limiter or make your tool idempotent; test with concurrent calls.
Forgetting to send instructions every requestInstructions are not persisted like an AssistantCentralise your prompt/template in a constant or config file and include it on each responses.create.
Overlooking the flat function_call schemaAssistants used a nested function keyUpdate your tool definition objects; run your linting suite to catch the old shape.
Neglecting streaming event name changesEvent types dropped the thread. prefixUpdate your SSE consumer to listen for response.* events; keep a unit test that validates the mapping.
Skipping retrieval parityFile search now requires explicit vector_store_ids and may return different chunk countsRun side‑by‑side retrieval evals; compare relevance scores and adjust max_num_results or filters as needed.
Missing cost alerts for tool callsFile search and web search are now metered per callAdd alerts on tool‑call volume; set daily budgets if necessary.
Assuming you can use both conversation and previous_response_id togetherThe API rejects the combinationPick 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.

Share this post:
Migrate from Assistants API to Responses API