← Blog

Anthropic Claude API vs OpenAI: Migration Guide

2026-10-14 · 5 min read · SubToAPI Team

If you're moving a product from OpenAI's API to Anthropic's Claude API, the good news is that the core request/response shape is similar enough that most migrations take a few hours, not weeks. The differences are concentrated in a handful of specific areas: how the system prompt is passed, how max_tokens is required, how streaming events are structured, and how tool calling is defined. This guide walks through each difference with code, so you can update your integration without guessing.

This is not an "AI is smarter here" comparison — it's a field guide for engineers who already have working OpenAI code and need to point it at Claude (or vice versa) without breaking production.

The core request shape

Both APIs use a messages array of role/content objects, so the mental model carries over directly. Here's a typical OpenAI chat completion:

curl https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "Summarize this ticket."}
    ]
  }'

And the equivalent Claude Messages API call:

curl https://api.anthropic.com/v1/messages \
  -H "x-api-key: $ANTHROPIC_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 1024,
    "system": "You are a helpful assistant.",
    "messages": [
      {"role": "user", "content": "Summarize this ticket."}
    ]
  }'

Three things to change immediately:

  1. System prompt is a top-level field, not a message. Claude doesn't accept role: "system" inside messages. Move it to the system parameter.
  2. max_tokens is required. OpenAI lets you omit it; Claude will reject the request without it. Pick a sane default per use case instead of leaving it unset.
  3. Authentication header is different. OpenAI uses Authorization: Bearer, Claude's native API uses x-api-key plus an anthropic-version header.

If you're routing through SubToAPI instead of calling Anthropic directly, this last point simplifies: SubToAPI exposes https://api.subtoapi.app/v1/messages with a standard Authorization: Bearer $SUBTOAPI_KEY header, so your existing OpenAI-style auth code needs almost no change. See the quickstart for the exact setup.

Roles and alternation

OpenAI tolerates multiple consecutive messages with the same role. Claude's Messages API expects strict user/assistant alternation — you can't send two user messages back to back without an assistant message in between. If your conversation history was built loosely (e.g., appending multiple user messages before a reply), you'll need to merge them into a single message with concatenated content before sending. This is the single most common bug when porting chat history over.

Streaming differences

OpenAI streams data: {...} chunks with a delta.content string. Claude's streaming (SSE) emits distinct typed events: message_start, content_block_start, content_block_delta, content_block_stop, message_delta, and message_stop. The text itself arrives inside content_block_delta events as delta.text.

const res = await fetch("https://api.subtoapi.app/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    max_tokens: 1024,
    stream: true,
    messages: [{ role: "user", content: "Write a haiku about migrations." }]
  })
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  // parse SSE "event:" / "data:" lines, filter for content_block_delta
}

If your frontend currently just concatenates delta.content strings from OpenAI, you'll need a small parser update to pull delta.text from the right event type. Details on the full event sequence are in the streaming docs.

Tool calling / function calling

OpenAI calls these "functions" or "tools" depending on API version, with a tools array containing type: "function" and a parameters JSON schema. Claude's tools array is flatter — each tool has name, description, and input_schema directly, without the extra function nesting layer:

{
  "tools": [
    {
      "name": "get_weather",
      "description": "Get current weather for a location.",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": { "type": "string" }
        },
        "required": ["location"]
      }
    }
  ]
}

Claude signals a tool call via a tool_use content block instead of OpenAI's tool_calls array, and you respond with a tool_result content block inside a follow-up user message rather than a role: "tool" message. The round-trip pattern is equivalent, just with different field names. Full schema and examples are in the tools documentation.

Response parsing and stop reasons

OpenAI returns choices[0].message.content and finish_reason (stop, length, tool_calls). Claude returns content as an array of blocks (text and/or tool_use) directly on the response object, plus stop_reason (end_turn, max_tokens, tool_use, stop_sequence). If your code does response.choices[0].message.content, change it to iterate response.content and extract the text fields from blocks with type: "text". See message response formats for the full schema.

Practical migration checklist

If you'd rather not touch authentication, billing, or key management while making this switch, SubToAPI gives you a single HTTPS endpoint with sub_live_... application keys, streaming, tool use, and per-request usage metadata — so the migration is just the request/response shape above, not a new account structure. Plans start at Solo €9/month with a free trial; see pricing or sign up to try it against your existing code.

Questions

Is Claude's API a drop-in replacement for OpenAI's? No, but it's close. The messages array and role-based conversation model are nearly identical; you mainly need to adjust the system prompt field, max_tokens requirement, streaming event parsing, and tool schema shape.

Do I need to rewrite my tool/function calling logic? Mostly just the schema and field names. Claude uses input_schema instead of nested function.parameters, and tool_use/tool_result content blocks instead of tool_calls messages — the overall request-then-respond pattern stays the same.

Can I run OpenAI-style auth code against Claude? Anthropic's native API requires x-api-key and an anthropic-version header, not Authorization: Bearer. If you want to keep bearer-token auth, routing through a service like SubToAPI (see /docs) lets you keep that header pattern unchanged.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →