π Assistants API Compatibility Shim
Pricing
Pay per event + usage
π Assistants API Compatibility Shim
Drop-in proxy keeping OpenAI Assistants API calls working past the August 26, 2026 sunset, plus a ThreadβConversation migration utility.
Pricing
Pay per event + usage
Rating
0.0
(0)
Developer
Stephan Corbeil
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 hours ago
Last modified
Categories
Share
102 days until OpenAI Assistants API shutdown. (Hard sunset: August 26, 2026 β countdown computed against 2026-05-16.)
Your code uses POST /v1/threads, POST /v1/threads/{id}/runs, and the rest of the OpenAI Assistants API. OpenAI is sunsetting that surface on August 26, 2026 in favor of the Responses API, which has a completely different shape (no threads, different message format, different tool wiring, different run lifecycle).
This actor is a drop-in HTTP proxy that keeps your Assistants-shaped calls working β and a one-shot migration utility for moving thread history into Responses conversations cleanly.
β³ The sunset countdown
| Hard cutoff date | 2026-08-26 |
| As of 2026-05-16 | 102 days remaining |
| Affected surface | /v1/assistants, /v1/threads, /v1/threads/{id}/messages, /v1/threads/{id}/runs, /v1/threads/{id}/runs/{id} |
| Replacement surface | /v1/responses, /v1/conversations |
| What breaks if you do nothing | Every hardcoded SDK call against openai.beta.threads.* or openai.beta.assistants.* returns 410 Gone after the cutoff |
If you own a SaaS, an internal automation, a Zapier/Make/n8n step, or a customer-facing chatbot that was built against the Assistants API in 2024β2025, you have one of two options:
- Rewrite every integration before the deadline. Industry estimate: 5β50 dev-days per integration at $150β300/hr (β $6kβ$120k per codebase).
- Point your client at this shim's URL instead of
api.openai.com. Zero code change. Buys you runway. Comes with a migration utility for when you're ready to cut over cleanly.
π§ Two ways to use it
1. Live proxy β zero code change
Swap your client's base URL. That's it.
- base_url = "https://api.openai.com/v1"+ base_url = "https://<actor-id>.apify.actor/v1"
Your existing client.beta.threads.create(...), client.beta.threads.messages.create(...), client.beta.threads.runs.create(...) calls keep working. Internally, the shim:
- Receives the Assistants-shaped request
- Translates it to the Responses API shape
- Calls OpenAI on your behalf using your own API key (passed via the standard
Authorization: Bearer sk-...header β never stored) - Translates the response back to the Assistants shape your code expects
- Returns it
2. Migration utility β one-shot bulk cutover
When you're ready to cut over to native Responses calls, use the migration utility to port thread history.
curl -X POST "https://<actor-id>.apify.actor/v1/migrate?source_thread_id=thread_abc123" \-H "Authorization: Bearer sk-..."curl -X POST "https://<actor-id>.apify.actor/v1/bulk_migrate" \-H "Authorization: Bearer sk-..." \-H "Content-Type: application/json" \-d '{"thread_ids":["thread_abc","thread_def","thread_ghi"]}'
You get back a conversation_id per thread, with the message history seeded into the new Responses conversation. Replay or branch from there with native Responses calls. Old threads are not deleted.
π Architecture
ββββββββββββββββ Assistants-shape ββββββββββββββββββββββββββ Responses-shape ββββββββββββββββ Your client β ββββββββββββββββββββΆ β Apify Standby actor β ββββββββββββββββββββΆ β api.openai ββ (unchanged) β Authorization: Bearerβ (this shim) β Same Bearer token β .com/v1 ββ SDK / cURL β β β’ translate request β β /responses ββ β ββββββββββββββββββββ β β’ call upstream β ββββββββββββββββββββ β βββββββββββββββββ Assistants-shape β β’ translate response β Responses-shape ββββββββββββββββ β’ thread state in KV βββββββββββββββββββββββββββββΌ (optional)βββββββββββββββββββββββββββ Apify Key-Value Store β thread_id β conversation_idβ (survives restarts) β message history mirrorββββββββββββββββββββββββββ
The actor runs in Apify Standby mode β it's an always-on HTTP server, not a one-shot scrape. You get a stable hostname (https://<actor-id>.apify.actor), the container stays warm under load, and idles down after 180 s of inactivity.
π Translation table
The translation logic is the heart of the shim. Every Assistants concept maps to a Responses concept:
| Assistants API concept | Responses API concept | Notes |
|---|---|---|
thread_id | conversation_id | Created lazily on first run; the shim stores the mapping. |
message.content (array of {type:"text", text:{value}}) | input (string, or array of {role, content}) | Multi-part text gets joined when possible; image parts pass through as input_image. |
assistant_id + assistant.tools | Request-time tools array | The shim keeps assistant configs in memory + KV; injects them on every run. |
assistant.instructions | instructions field on the Responses request | Direct passthrough. |
run.status lifecycle (queued β in_progress β completed) | Single synchronous Responses call | The shim returns a completed run immediately β Responses is not long-polling. |
run_steps | Reassembled from response.output[] items | Tool calls and message outputs are flattened into the run-step shape your client expects. |
Assistants tools: [{type:"function", function:{...}}] | Responses tools: [{type:"function", name, description, parameters}] | Schema repacks; semantics identical. |
Assistants tools: [{type:"code_interpreter"}] | Responses tools: [{type:"code_interpreter", container:{type:"auto"}}] | Translates, but containers are auto-provisioned β explicit container IDs aren't carried over. |
Assistants tools: [{type:"file_search", vector_store_ids:[...]}] | Not supported in v1 | Vector stores don't migrate 1:1; see "Unsupported" below. |
Assistants messages.attachments (file_ids) | input_image / input_file parts | File-id passthrough for images; arbitrary file attachments β v2. |
Assistants metadata | metadata | Stored and round-tripped. |
β Supported endpoints (v1)
POST /v1/threadsβ create threadGET /v1/threads/{thread_id}/messagesβ list messagesPOST /v1/threads/{thread_id}/messagesβ append messagePOST /v1/threads/{thread_id}/runsβ synchronous run (returnscompleted)GET /v1/threads/{thread_id}/runs/{run_id}β get runPOST /v1/assistantsβ create assistant (stored in actor state)GET /v1/modelsβ passthrough to OpenAIPOST /v1/migrate?source_thread_id=...β migrate one threadPOST /v1/bulk_migrateβ migrate up to thousands of threads in one call
π§ Not yet supported (v2 roadmap)
/v1/vector_stores/*andfile_searchtoolcode_interpreterwith explicit container IDs (auto-containers do work)- Streaming (
stream:true) onrunsβ synchronous-only in v1 assistants.list/threads.delete(low priority β most clients don't need them)
If you hit an unsupported endpoint, the shim returns HTTP 501 with unsupported_endpoint so your error handling can detect it cleanly.
π― Use cases
- SaaS app with hardcoded Assistants integration. You shipped a customer-facing AI assistant feature in 2024 against
openai.beta.threads. Rewriting before August 26 is a full sprint. Swap base URL β done. - Zapier / Make / n8n workflow. Your no-code automation has an "OpenAI Assistant" step. Point the custom-base-URL field at the shim and the workflow keeps running.
- Customer chatbot with persistent thread history. Per-user
thread_ids are stored in your DB. You can't lose conversation continuity. The shim preservesthread_idsemantics; migration utility ports history toconversation_idwhen you're ready. - Internal AI tools (Slack bot, ops dashboard). Built once, forgotten about, still in production. Shim keeps them alive past sunset while you reprioritize a rewrite.
- Multi-tenant AI platform. You expose "bring your own assistant" to customers. Each tenant's hardcoded Assistants integrations need a stopgap β point them all at the shim.
- Agentic frameworks pinned to old OpenAI SDK versions. LangChain/LlamaIndex chains that wrap
AssistantAgentkeep working without a framework upgrade.
βοΈ Input schema
| Field | Type | Default | Description |
|---|---|---|---|
openaiApiKey | string (secret) | "" | Fallback OpenAI key used when an incoming request has no Authorization header. Most live-proxy users leave this empty β clients send the key per request. Set it for migration mode if your migration script doesn't send auth. |
mode | enum | "both" | "live-proxy", "migration-only", or "both". |
persistThreadState | boolean | true | Mirror thread/assistant state to the actor's Key-Value Store so it survives restarts. |
π Quick start
Step 1 β Start the actor in Standby mode
Run the actor once with the default input. Apify keeps the container warm. You'll get a Standby URL like https://nexgendata--assistants-api-compatibility-shim.apify.actor.
Step 2 β Repoint your client
Python (OpenAI SDK)
from openai import OpenAIclient = OpenAI(api_key="sk-...",base_url="https://<actor-id>.apify.actor/v1",)# This call used to go to api.openai.com β now it goes through the shim.thread = client.beta.threads.create()client.beta.threads.messages.create(thread_id=thread.id,role="user",content="What's the capital of France?",)run = client.beta.threads.runs.create(thread_id=thread.id,assistant_id="asst_xxx",)print(run.status) # "completed"
cURL β live proxy
# Create a threadcurl -X POST "https://<actor-id>.apify.actor/v1/threads" \-H "Authorization: Bearer sk-..." \-H "Content-Type: application/json" \-d '{}'# Append a user messagecurl -X POST "https://<actor-id>.apify.actor/v1/threads/thread_xxx/messages" \-H "Authorization: Bearer sk-..." \-H "Content-Type: application/json" \-d '{"role":"user","content":"Hello"}'# Run an assistantcurl -X POST "https://<actor-id>.apify.actor/v1/threads/thread_xxx/runs" \-H "Authorization: Bearer sk-..." \-H "Content-Type: application/json" \-d '{"assistant_id":"asst_xxx"}'
cURL β migration
# One threadcurl -X POST "https://<actor-id>.apify.actor/v1/migrate?source_thread_id=thread_xxx" \-H "Authorization: Bearer sk-..."# Bulkcurl -X POST "https://<actor-id>.apify.actor/v1/bulk_migrate" \-H "Authorization: Bearer sk-..." \-H "Content-Type: application/json" \-d '{"thread_ids":["thread_a","thread_b","thread_c"]}'
Python β bulk migration script
import httpxids = [...] # load from your DBr = httpx.post("https://<actor-id>.apify.actor/v1/bulk_migrate",headers={"Authorization": "Bearer sk-..."},json={"thread_ids": ids},timeout=600,)print(r.json()["success"], "migrated;", r.json()["failed"], "failed")
β FAQ
Q: How long until OpenAI shuts down the Assistants API?
A: 102 days as of 2026-05-16. Hard cutoff is 2026-08-26. After that, every /v1/threads/* call returns 410 Gone.
Q: What happens to my thread history if I just keep using the live proxy? A: The shim stores thread state (messages, assistant_id mapping, conversation_id mapping) in the Apify Key-Value Store. It survives restarts. But the upstream OpenAI threads themselves go away on 2026-08-26 β meaning the shim becomes the new source of truth for history. Run the migration utility before the cutoff to seed the data into Responses conversations.
Q: Can I migrate without using the live proxy?
A: Yes β set mode: "migration-only" and the live-proxy routes are disabled. You just call /v1/bulk_migrate once and get the mapping table.
Q: What's the latency overhead of the shim? A: ~15β40 ms p50 added on top of the OpenAI call (one extra hop, JSON repack). Standby keeps the container warm so there's no cold start after the first request.
Q: Are file_search / vector stores supported?
A: Not in v1 β they're punted to v2 because vector_store_ids don't migrate 1:1 to Responses' file/container model. Hitting /v1/vector_stores/* returns HTTP 501 with unsupported_endpoint. If you depend on file_search, migrate those specific call sites manually.
Q: How does pricing compare to migration consulting? A: A typical agency engagement to rewrite Assistants integrations runs $6kβ$120k per codebase. The shim is $0.05 per proxied request, $0.20 per thread migrated, and $12 per 100-thread bulk batch. Even a heavy user running 100k requests and migrating 10k threads pays ~$5,000 β and gets a runway, not a one-time rewrite.
Q: What if OpenAI extends the deadline? A: Then you keep running the shim cheaply. There's no commitment β pay-per-event pricing means you pay $0 when your traffic is $0.
Q: Do I need my own OpenAI API key?
A: Yes β BYO. The shim is a translation layer; it does not resell OpenAI access. Pass your key in the Authorization: Bearer sk-... header on every request (the standard SDK behavior). Keys are never logged or persisted.
Q: Is the container multi-tenant safe? Can two of my customers share one shim?
A: Thread state is keyed by thread_id, which is globally unique. Two tenants with separate thread_ids won't collide. But for strict tenant isolation we recommend one shim run per tenant β Apify makes that cheap.
π Comparison
| DIY rewrite | OpenAI's official migration guide | Cleric / Bedrock proxies | This shim | |
|---|---|---|---|---|
| Code changes required | All call sites | All call sites | Some config | One base URL line |
| Engineering effort | 5β50 dev-days | 5β50 dev-days | 1β5 dev-days | 5 minutes |
| Migrates existing threads | Manual | Manual scripts you write | No | Yes (bulk utility) |
| Pricing | $6kβ$120k consulting | Free (your time) | $500+/mo flat | PPE: $0 idle, ~$0.05/request |
| Hosted | You host | N/A | They host | Apify hosts |
| Source available | β | β | Closed | Sourced from this actor |
π€ Sister actors from NexGenData
If this shim solved your migration problem, check out our other Apify-hosted AI infra:
nexgendata/news-mcp-serverβ News MCP server for Claude, Cursor, and other MCP clients.nexgendata/finance-mcp-serverβ Finance + market data MCP server.nexgendata/yahoo-finance-mcp-serverβ Yahoo Finance MCP server with tickers, fundamentals, and history.nexgendata/developer-tools-mcp-serverβ Developer tooling MCP server.nexgendata/google-maps-mcp-serverβ Google Maps MCP server for location, geocoding, and place search.
πΈ Don't have an Apify account?
Sign up free β $5 free credits to host this shim and keep your Assistants integrations alive past the August 2026 sunset.
π License
Source distributed with the actor. BYO OpenAI key β we never store, log, or proxy-decrypt your credentials.