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#

  1. Grep first. Search your codebase for beta.threads, beta.assistants, and create_and_poll. The count is your real scope — usually smaller than you feared, occasionally hiding in a cron job you forgot.
  2. Port the simplest read path — one Assistant with no tools — to a bare responses.create(). Confirm output_text matches.
  3. Add tools back, reusing your existing vector store IDs. Verify file_search returns the same citations.
  4. Rebuild state last. Pick previous_response_id or Conversations per surface, not globally.
  5. 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.