Migrating from the OpenAI API to the Claude API

The request shape is close enough to migrate in an afternoon — and different enough that a blind cutover breaks in three predictable places: the system message, the required token limit, and tool calling.

In short: Three predictable breakages: the system prompt moves to a top-level field, max_tokens becomes required, and tool calling changes schema. Everything else, messages, streaming, temperature, carries over. Run both APIs in shadow mode before cutting traffic.
You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment

Side-by-side request

OpenAI style:

Don't have an account yet? You need an API key from the gateway before the steps below will work — how the discount works and what you take on. Create one free → (no prepay, $1 minimum top-up)
resp = client.chat.completions.create(
    model="gpt-5.6-luna",
    messages=[
        {"role": "system", "content": "You are a careful editor."},
        {"role": "user", "content": "Proofread this."},
    ],
)

Claude style:

resp = client.messages.create(
    model="claude-sonnet-5",
    system="You are a careful editor.",   # top-level, not a message
    max_tokens=1024,                      # required
    messages=[{"role": "user", "content": "Proofread this."}],
)
print(resp.content[0].text)

The mapping table

ConceptOpenAIClaude
System prompt{"role":"system"} inside messagesTop-level system parameter
Token limitmax_tokens, optionalmax_tokens, required
Text outputchoices[0].message.contentcontent[0].text
Input usageprompt_tokensinput_tokens
Output usagecompletion_tokensoutput_tokens
Stop sequencesstopstop_sequences
Tool callingfunctions / toolstools with input_schema
JSON moderesponse_formatPrefill or tool-based extraction
Auth headerAuthorization: Bearerx-api-key + anthropic-version

Tool calling: the part that needs real rewriting

OpenAI puts arguments in function.arguments as a JSON string. Claude returns a tool_use content block with an already-parsed input object, and you return results as a tool_result block rather than a message with a role. Budget most of your migration time here.

tools = [{
    "name": "get_weather",
    "description": "Current weather for a city",
    "input_schema": {
        "type": "object",
        "properties": {"city": {"type": "string"}},
        "required": ["city"],
    },
}]

What it costs after you move

Comparing the OpenAI and Claude models teams usually evaluate against each other
ModelGateway rate
in / out per 1M tokens
Official list
in / out per 1M tokens
Diff
gpt-5.6-luna$0.1 / $0.6$0.2 / $1.250%
claude-sonnet-5$1 / $5$2 / $1050%
claude-opus-5$2.5 / $12.5$5 / $2550%

Rates checked 2026-11-01. Gateway rates move with upstream promotions (verify the current number in your dashboard before committing to a budget).

Do not assume the migration saves money. Compare tokens per task on real traffic, not list price per million. Models differ in how verbose they are, and output is the expensive side.

A cutover plan that does not hurt

  1. Shadow mode. Send the same prompts to both, store both outputs, ship the OpenAI one.
  2. Diff quality. Review where they disagree; those are your risk areas.
  3. Route 10%. Real traffic, real latency, real cost.
  4. Compare cost per task. Not per token (per completed task).
  5. Ramp. Increase only after a week of clean metrics.

Common errors during migration

ErrorCause
max_tokens is requiredClaude requires it; OpenAI made it optional
Invalid system message positionSystem prompt left inside messages
401 / authentication_errorUsing Authorization: Bearer instead of x-api-key
Tools ignoredSchema passed as parameters rather than input_schema

Migration failures that look like model problems but are not

SymptomWhat is actually happeningHow to confirmFix
The system prompt is silently ignoredOpenAI puts system instructions in a message with role=system; Anthropic expects a separate top-level field.Ask the model to repeat the first line of its instructions, if it cannot, they never arrived.Move the system message out of the messages array into the dedicated parameter.
max_tokens rejected or responses truncatedAnthropic requires an explicit output cap; OpenAI treats it as optional.Look for a validation error naming the parameter, or outputs that stop mid-sentence.Set the cap explicitly on every call rather than relying on a default.
Tool calls come back as text instead of structured blocksThe two APIs describe tools differently, and a schema copied across often fails to bind.Inspect the response for a tool-use block versus plain content.Re-declare tools in the target format; do not assume the schema transfers.
Streaming consumer breaks on the first chunkThe event names and the shape of each chunk differ between the two APIs.Log the raw first three chunks and compare them to what your parser expects.Port the streaming handler, not just the request builder.
Image or document input returns an errorMultimodal content blocks are structured differently and are not interchangeable.Test with a single small attachment before converting the whole path.Rebuild the content blocks in the target format.
Token counts differ from the old provider for the same textTokenisers differ; counts are close but not identical.Compare usage numbers on one fixed input across both providers.Budget with a margin rather than assuming parity.

Rolling it out without finding out the hard way

The failure mode you want to avoid is discovering a problem through a production bill or a customer-visible error. Four steps, in order:

  1. Prove it on one read-only endpoint. One call, from a script, with an explicit timeout and the model ID echoed back. You are testing reachability, authentication and model availability (three things that can fail independently).
  2. Measure before you switch. Record tokens per task and cost per task on the current path first. Without that baseline, "it got cheaper" is an impression, not a result.
  3. Move one workload, not everything. Pick the workload with the most predictable shape, batch jobs over interactive traffic. Leave the interactive path on the old configuration until the batch numbers are in.
  4. Decide the rollback condition in advance. Write down what makes you revert (error rate above X, cost per task above Y, latency above Z) before you start, so the decision is not made under pressure.

Keep the base URL in configuration, never inline. That single choice is what makes step four take a minute instead of an afternoon.

FAQ

Can I reuse my OpenAI code with Claude?

Partly. If you went through an OpenAI-compatible layer, changing the base URL and key may be enough. If you called the OpenAI API directly, you need the changes in the mapping table above (mostly the system field, max_tokens, and tool calling).

What is the biggest gotcha in the migration?

max_tokens is required on Claude and optional on OpenAI. Requests that worked for months will fail with a validation error until you set it.

Does the response format change?

Yes. Instead of choices[0].message.content you read content[0].text, and usage is reported as input_tokens and output_tokens rather than prompt_tokens and completion_tokens.

How do I keep costs under control during migration?

Run both in parallel on a sample first, measure tokens per task on each, then cut over. Migrating blind is how teams double their bill on day one.

Can I migrate gradually?

Yes, and you should. Route a percentage of traffic, compare quality and cost for a week, then increase. The per-task cost difference is usually visible within a day of real traffic.

Related

Get API access

Rate cards: Claude and GPT