Migrating OpenAI to Claude API often sounds like a full SDK swap: different client, different message schema, different tool-calling format. If your stack already uses the OpenAI Python or TypeScript client, you can switch to Claude by changing a base URL and a model string—no business logic changes required. This guide gives the exact steps to cut over with zero rewrites and how to prove the migration worked.
Step 1: Audit your current OpenAI integration
Open your codebase and find every place that constructs an OpenAI client or calls chat.completions.create. You care about five things:
- The
base_url(if overridden) - The
modelstring - Presence of
toolsorfunctions - Use of
stream=Trueorstream: true - Custom
max_tokens,stop, ortemperaturesettings
A typical Python call looks like this:
from openai import OpenAI
client = OpenAI(api_key="sk-openai-...")
resp = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Summarize this log"}],
max_tokens=512,
)
If you use the default api.openai.com/v1 endpoint, you are already speaking the OpenAI wire protocol. That protocol is what we will keep. Grep for import openai, OpenAI(, and .chat.completions to map your surface area. Note any middleware that inspects response headers or error shapes—those will be tested later.
Step 2: Choose an OpenAI-compatible gateway that fronts Claude
Anthropic’s native API uses a different request shape (top-level system field, anthropic-version header, distinct tool schema). You do not want to touch that from app code. Instead, route through a gateway that translates the OpenAI protocol to Claude’s backend.
A gateway like n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models, including Claude, and handles the translation, automatic fallback when a provider is degraded, and per-token usage metering. Your app still thinks it is talking to OpenAI. The translation layer absorbs the schema differences so your openai SDK calls work unchanged.
Step 3: Swap the base URL and model identifier
Change two strings. Nothing else.
from openai import OpenAI
client = OpenAI(
base_url="https://api.n4n.ai/v1", # was: default OpenAI
api_key="your-gateway-key",
)
resp = client.chat.completions.create(
model="claude-3-5-sonnet-20241022", # was: gpt-4o
messages=[{"role": "user", "content": "Summarize this log"}],
max_tokens=512,
)
Model ID mapping is straightforward:
| OpenAI model | Claude equivalent |
|---|---|
gpt-4o |
claude-3-5-sonnet-20241022 |
gpt-4o-mini |
claude-3-haiku-20240307 |
gpt-4-turbo |
claude-3-opus-20240229 |
The gateway accepts the OpenAI chat completions path (/v1/chat/completions) and returns the same JSON shape. For a quick smoke test without code changes, use curl:
curl https://api.n4n.ai/v1/chat/completions \
-H "Authorization: Bearer $GW_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"messages": [{"role": "user", "content": "ping"}]
}'
You should get a choices[0].message.content string back, identical in structure to OpenAI’s response.
Step 4: Handle subtle protocol differences
The gateway hides most differences, but three areas deserve a look.
System prompts
OpenAI puts system instructions in a message with "role": "system". Claude expects a top-level system field. The gateway extracts the system message and maps it correctly. Keep your existing messages array; do not refactor.
Tools and function calls
OpenAI defines tools as:
{
"tools": [
{"type": "function", "function": {"name": "get_weather", "parameters": {...}}}
]
}
Claude’s native format differs, but the gateway translates the OpenAI schema into Anthropic’s tools block and back. Your tool_calls parsing code stays the same. Verify one complex tool call end to end before declaring victory.
Stop sequences and token limits
OpenAI accepts stop as an array of strings; Claude supports stop sequences similarly and the gateway passes them through. Max output tokens vary by Claude model—stay within the model’s published ceiling by reusing your existing max_tokens value only if it is safely below that limit.
Cache control and routing
Claude supports prompt caching via a cache_control breakpoint. The gateway honors client routing directives and forwards provider cache-control hints. If you want to cache a long system prompt, pass it through OpenAI-style extra_headers:
resp = client.chat.completions.create(
model="claude-3-5-sonnet-20241022",
messages=[{"role": "system", "content": LONG_PROMPT},
{"role": "user", "content": "Go"}],
extra_headers={"anthropic-cache-control": "ephemeral"},
)
This header is forwarded to Claude; your app code does not need Anthropic SDK.
Step 5: Validate streaming and token accounting
If you stream, flip stream=True and iterate as before:
stream = client.chat.completions.create(
model="claude-3-5-sonnet-20241022",
messages=[{"role": "user", "content": "Count to 5"}],
stream=True,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")
The chunk shape (choices[0].delta.content) is byte-for-byte compatible with OpenAI’s SSE format. Check the usage object on the final chunk (or response when not streaming). The gateway returns prompt_tokens and completion_tokens using Claude’s tokenizer counts. Wire those into your existing metering dashboards.
Step 6: Cut over in production with shadow traffic
Do not flip the switch globally on day one. Use an environment variable for base_url and model:
import os
from openai import OpenAI
client = OpenAI(
base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"),
api_key=os.getenv("LLM_KEY"),
)
MODEL = os.getenv("LLM_MODEL", "gpt-4o")
Run a shadow test: send 5% of traffic to Claude via the gateway, compare outputs and latency. Because the gateway provides automatic fallback when a provider is rate-limited, a Claude outage will not take down your app if you configure fallback to a second model. Your existing retry logic against OpenAI-style 429s keeps working.
Step 7: Monitor, verify, and rollback
Watch gateway access logs for Claude model IDs. Confirm error rates match or beat the OpenAI baseline. If output quality regresses on a specific prompt, flip LLM_MODEL back to gpt-4o without a deploy—the code is identical. The per-token usage metering lets you compare cost per request directly.
Verification checklist
You have successfully finished migrating OpenAI to Claude API when:
- All
OpenAI()client constructions point at the gateway base URL. - No
anthropicSDK imports exist in your app code. - A representative suite of non-streaming and streaming calls returns valid
choiceswith expected content. - At least one tool-calling path executes and the parsed
tool_callsmatches your pre-migration schema. usage.prompt_tokensandusage.completion_tokensare populated and logged.- Production traffic shows Claude model IDs in gateway access logs, confirming routing.
- Shadow traffic comparison shows acceptable latency and output parity.
Migrating OpenAI to Claude API this way took our team an afternoon, not a sprint. Keep the OpenAI client, change two strings, and let the translation layer absorb the provider differences.