How to Switch from OpenAI to Claude API
Switching from OpenAI to Claude API mainly means changing three things: the request shape, how you handle the system prompt, and how you parse the response. The underlying pattern — send messages, get a completion, optionally stream tokens or call tools — stays the same, so a working OpenAI integration usually maps to Claude in a few hours, not a rewrite.
This guide walks through the concrete differences you'll hit during migration, with code for both sides so you can compare directly.
Why teams switch
Most migrations happen for one of three reasons: Claude's longer context windows, its tool-use behavior on complex multi-step tasks, or wanting a second provider for redundancy and pricing leverage. Whatever your reason, the migration itself is mechanical once you know where the APIs diverge.
Step 1: Map the request format
OpenAI's Chat Completions API takes a flat messages array where the system prompt is just another message with role: "system":
// OpenAI
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are a helpful support agent." },
{ role: "user", content: "How do I reset my password?" }
],
max_tokens: 500
});
Claude's Messages API pulls the system prompt out into its own top-level field:
// Claude
const response = await anthropic.messages.create({
model: "claude-sonnet-4-5",
system: "You are a helpful support agent.",
messages: [
{ role: "user", content: "How do I reset my password?" }
],
max_tokens: 500
});
max_tokens is required in Claude requests — there's no server-side default like OpenAI's implicit cap. If you omit it, the call fails, so audit any code that relied on OpenAI's defaults.
Step 2: Update response parsing
OpenAI returns the assistant text at choices[0].message.content. Claude returns a content array of blocks, since a single response can mix text and tool calls:
// OpenAI
const text = response.choices[0].message.content;
// Claude
const text = response.content
.filter(block => block.type === "text")
.map(block => block.text)
.join("");
If you're only ever doing plain text completions, this is a one-line change. If you're doing tool use, you'll need to iterate the content blocks and check block.type === "tool_use" for structured calls — see /docs/tools for the exact shape.
Step 3: Rework streaming
OpenAI streams delta chunks with data: lines that carry partial choices[0].delta.content. Claude uses a named event stream (message_start, content_block_delta, message_stop, etc.), which is more verbose but easier to reason about because each event type is unambiguous:
const stream = await anthropic.messages.stream({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Write a haiku about APIs" }]
});
for await (const event of stream) {
if (event.type === "content_block_delta") {
process.stdout.write(event.delta.text);
}
}
If your frontend just reads a token stream and appends it to a buffer, the migration is invisible to the UI layer — you're only changing which event type you extract text from. Full details are in /docs/streaming.
Step 4: Handle function/tool calling differences
OpenAI's tools use a function wrapper with parameters as JSON Schema. Claude's tools are flatter — name, description, and input_schema:
// Claude tool definition
const tools = [{
name: "get_weather",
description: "Get current weather for a location",
input_schema: {
type: "object",
properties: {
location: { type: "string" }
},
required: ["location"]
}
}];
The control flow is the same: the model returns a tool call, you execute it, and send the result back as a message with role: "user" and a tool_result content block. The naming differs but the loop is identical, so most tool-calling code ports over by renaming fields rather than restructuring logic.
Step 5: Check model-specific quirks
A few things trip people up during migration:
- No
nparameter. Claude doesn't support generating multiple completions in one call. If you rely onn > 1for sampling diversity, you'll need multiple requests. - No
logprobs. If your pipeline depends on token-level probabilities, you'll need a different approach or a fallback provider for that specific feature. - Stop sequences work differently. Claude's
stop_sequencesis an array like OpenAI's, but behavior around partial matches can differ slightly — test edge cases in your prompts. - Rate limits and error codes are structured differently. Update your retry logic to match Claude's error response format rather than reusing OpenAI's error codes verbatim.
Step 6: Decide how you're authenticating and billing
If you're calling the Anthropic API directly, you'll need an Anthropic account, billing setup, and to manage API keys per environment yourself. If you want to skip that and reuse a Claude subscription you already pay for, SubToAPI turns it into a standard HTTPS API: you get sub_live_... keys, the same Messages-style request/response shape, streaming, tool use, and usage metadata per key — without setting up separate Anthropic billing. This is particularly useful mid-migration, when you want to test Claude in production against real traffic before committing to a long-term billing setup. See /docs/quickstart to get a key running in a few minutes, and /docs/messages for the exact request format.
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 500,
"messages": [{"role": "user", "content": "How do I reset my password?"}]
}'
Step 7: Run both providers side by side
Before cutting over fully, route a percentage of traffic to Claude and compare output quality, latency, and cost on real requests. Keep your OpenAI integration behind a feature flag for a few weeks — this is the safest way to catch prompt regressions that only show up with real user inputs, not just your test cases.
Testing checklist
- Verify system prompts produce comparable behavior (Claude tends to follow instructions more literally)
- Confirm
max_tokensis set on every request - Re-test any prompt that depends on specific formatting or few-shot examples
- Check tool-calling flows end-to-end, not just the request shape
- Compare latency and streaming behavior under real load, not just single test calls
Full request/response documentation is at /docs, and pricing for a hosted, subscription-based setup is at /pricing if you'd rather skip managing a separate Anthropic bill during and after migration.
FAQ
Do I need to rewrite my entire application to switch from OpenAI to Claude? No. If your architecture separates the LLM call from the rest of your logic, you're mainly rewriting one client function — the request builder, response parser, and streaming handler. Business logic, prompts, and UI usually don't need major changes.
Can I run OpenAI and Claude side by side during migration? Yes, and it's the recommended approach. Add a provider flag to your request layer, route a subset of traffic to Claude, and compare output quality and latency before fully cutting over.
Is Claude's tool-calling compatible with OpenAI's function-calling format? Not directly — field names differ (input_schema vs parameters) and the response structure uses content blocks instead of a tool_calls array. The control flow (call, execute, return result) is the same, so migration is a rename-and-restructure task rather than a redesign.