The short version: OpenAI sunsets the Assistants API on August 26, 2026. After that, any call to /v1/assistants, /v1/threads, or /v1/threads/runs returns an error — no grace period, no read-only window, no auto-forwarding (OpenAI community announcement). The replacement is the Responses API, already GA. The migration is a mental-model shift, not a rewrite: four server objects collapse into one client.responses.create() call, and your vector stores carry over unchanged. Do it now — the hard part is finding every place your code assumes a Thread, not writing the new call.
Why this is a re-model, not a rename#
The Assistants API was stateful and spread one conversation across four separate server objects, each with its own lifecycle. You created an Assistant to hold the config, opened a Thread to hold the history, appended Messages to that thread, then created a Run and polled it in a loop until the model finished and you could read the reply back off the thread. Four round trips before a single answer. Here is that full ceremony in Python:
# OLD — Assistants API (stops working Aug 26, 2026)
assistant = client.beta.assistants.create(
name="Support bot",
instructions="You are a concise support agent.",
model="gpt-4o",
tools=[{"type": "file_search"}],
tool_resources={"file_search": {"vector_store_ids": ["vs_123"]}},
)
thread = client.beta.threads.create()
client.beta.threads.messages.create(
thread.id, role="user", content="How do I reset my password?"
)
run = client.beta.threads.runs.create_and_poll(
thread_id=thread.id, assistant_id=assistant.id
)
messages = client.beta.threads.messages.list(thread_id=thread.id)
The Responses API throws that ceremony out. There is no Assistant to register, no Thread to open, and no Run to poll — the call answers synchronously (or streams):
# NEW — Responses API
resp = client.responses.create(
model="gpt-4o",
instructions="You are a concise support agent.",
input="How do I reset my password?",
tools=[{"type": "file_search", "vector_store_ids": ["vs_123"]}],
)
print(resp.output_text)
Same behavior, one call. Note the two things that moved: the Assistant's instructions now ride on the call, and tool_resources is gone — file_search takes the vector store IDs directly.
The four things you actually have to move#
1. The Assistant's config. There is no server-side Assistant object anymore. Its instructions, model, and tools now live on each responses.create() call. If several endpoints share one config, keep it in your own code — a constant or a DB row — and spread it in. This feels like a downgrade until you realize you no longer manage a second lifecycle of objects that drift out of sync with your code.
2. The Thread → conversation state. This is the only genuinely new decision. Two options:
# Option A — previous_response_id (short-lived sessions)
first = client.responses.create(
model="gpt-4o", instructions=SYSTEM, input="How do I reset my password?"
)
second = client.responses.create(
model="gpt-4o", previous_response_id=first.id, input="And on mobile?"
)
# Option B — a Conversation object (long-lived, replayable)
conv = client.conversations.create()
client.responses.create(model="gpt-4o", conversation=conv.id,
instructions=SYSTEM, input="How do I reset my password?")
client.responses.create(model="gpt-4o", conversation=conv.id, input="And on mobile?")
The trap: stored responses have a 30-day TTL by default, so a previous_response_id chain silently breaks a month later. Conversation items have no TTL. If a user can return to a thread weeks later, use a Conversation. We break down that choice — and how to roll your own state instead — in the state-management companion piece.
3. Tools. Function tools keep the same JSON schema. The two hosted tools move their config onto the tool object: file_search takes vector_store_ids (shown above), and code_interpreter takes a container:
tools=[{"type": "code_interpreter", "container": {"type": "auto"}}]
4. Vector stores — nothing to do. This is the good news worth repeating. The Vector Stores API is unchanged; your vs_... IDs work as-is with file_search. No re-upload, no re-index, no data migration. If retrieval was the reason you were on the Assistants API, that reason ports for free.
The migration order that actually works#
- Grep first. Search your codebase for
beta.threads,beta.assistants, andcreate_and_poll. The count is your real scope — usually smaller than you feared, occasionally hiding in a cron job you forgot. - Port the simplest read path — one Assistant with no tools — to a bare
responses.create(). Confirmoutput_textmatches. - Add tools back, reusing your existing vector store IDs. Verify
file_searchreturns the same citations. - Rebuild state last. Pick
previous_response_idor Conversations per surface, not globally. - Delete the Assistant objects once traffic is off them, so nothing quietly resurrects the old path.
The blocker is never the new call — it is discovering which corner of your app still assumes a Thread. Run the grep today, not on August 25.
If you were reaching for the Agents SDK#
If your Assistant was one model plus retrieval and a tool or two, the Responses API is the direct target — stop there. Only move to the OpenAI Agents SDK if you also need multi-agent handoffs, guardrails, and a managed run loop. It is built on the Responses API, so everything above — input, tools, instructions, conversation state — is the same substrate. Learn the Responses primitives first; the SDK is a layer, not a detour.



