Migrating from OpenAI to Claude API in Python

Step-by-step guide to switching from OpenAI GPT to Anthropic Claude in Python. Side-by-side API differences, model mapping, and a drop-in wrapper class.

💥 50p impulse-buy: Power Prompts PDF (first 10 buyers) 30 battle-tested Claude Code prompts · 8-page PDF · paste into CLAUDE.md and never re-type a prompt again · 50p impulse-buy, no commitment

Switching from OpenAI to Claude requires adapting to a different SDK shape — but the concepts map cleanly. This guide walks through every difference and provides a drop-in wrapper so you can migrate incrementally.

Side-by-side: minimal chat completion

OpenAI Python SDKAnthropic Python SDK
Installpip install openaipip install anthropic
AuthOPENAI_API_KEYANTHROPIC_API_KEY
ClientOpenAI()anthropic.Anthropic()
Create messageclient.chat.completions.create()client.messages.create()
Model parammodel="gpt-4o"model="claude-sonnet-4-6"
System promptRole "system" in messagesSeparate system= param
Max tokensmax_tokens=max_tokens= (required, no default)
Read outputresponse.choices[0].message.contentresponse.content[0].text
Usageresponse.usage.prompt_tokensresponse.usage.input_tokens

Before (OpenAI)

from openai import OpenAI

client = OpenAI()  # reads OPENAI_API_KEY

response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=512,
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "What is the capital of France?"}
    ]
)
print(response.choices[0].message.content)

After (Claude)

import anthropic

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    system="You are a helpful assistant.",   # system prompt moves here
    messages=[
        {"role": "user", "content": "What is the capital of France?"}
    ]
)
print(response.content[0].text)

Model mapping

OpenAI modelClaude equivalentNotes
gpt-4oclaude-sonnet-4-6Similar quality, lower price/token
gpt-4o-miniclaude-haiku-4-5-20251001Haiku is faster and cheaper
o1 / o3 (reasoning)claude-opus-4-7Opus with extended thinking for step-by-step reasoning
gpt-4-turboclaude-opus-4-7Long context + top quality

Drop-in wrapper class

Migrate one call site at a time with this compatibility shim:

import anthropic

MODEL_MAP = {
    "gpt-4o": "claude-sonnet-4-6",
    "gpt-4o-mini": "claude-haiku-4-5-20251001",
    "gpt-4-turbo": "claude-opus-4-7",
    "gpt-4": "claude-opus-4-7",
    "gpt-3.5-turbo": "claude-haiku-4-5-20251001",
}

class OpenAICompatClient:
    """Thin shim mapping chat.completions.create() → client.messages.create()."""

    def __init__(self):
        self._client = anthropic.Anthropic()
        self.chat = self
        self.completions = self

    def create(self, *, model, messages, max_tokens=1024, **kwargs):
        claude_model = MODEL_MAP.get(model, model)
        # Extract system prompt if passed in messages list (OpenAI style)
        system = None
        user_messages = []
        for m in messages:
            if m["role"] == "system":
                system = m["content"]
            else:
                user_messages.append(m)

        resp = self._client.messages.create(
            model=claude_model,
            max_tokens=max_tokens,
            system=system or anthropic.NOT_GIVEN,
            messages=user_messages
        )
        # Return an OpenAI-shaped response object
        return _CompatResponse(resp)


class _CompatChoice:
    def __init__(self, text):
        self.message = type("M", (), {"content": text, "role": "assistant"})()

class _CompatUsage:
    def __init__(self, resp):
        self.prompt_tokens = resp.usage.input_tokens
        self.completion_tokens = resp.usage.output_tokens
        self.total_tokens = resp.usage.input_tokens + resp.usage.output_tokens

class _CompatResponse:
    def __init__(self, resp):
        self.choices = [_CompatChoice(resp.content[0].text)]
        self.usage = _CompatUsage(resp)
        self.model = resp.model


# Usage — zero changes to existing call site:
client = OpenAICompatClient()
response = client.chat.completions.create(
    model="gpt-4o",
    max_tokens=256,
    messages=[
        {"role": "system", "content": "Be concise."},
        {"role": "user", "content": "What is Claude?"}
    ]
)
print(response.choices[0].message.content)

Tool use migration

# OpenAI function calling
functions = [{
    "name": "get_weather",
    "description": "Get current weather",
    "parameters": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
    }
}]

# Claude tool use — same JSON schema, different key names
tools = [{
    "name": "get_weather",
    "description": "Get current weather",
    "input_schema": {                         # ← "input_schema" not "parameters"
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
    }
}]

response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=256,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Paris?"}]
)

# Read tool call result
for block in response.content:
    if block.type == "tool_use":
        print(block.name, block.input)  # "get_weather" {"location": "Paris"}

For a full cost comparison before committing to migration, use the Claude API Cost Calculator and compare your current OpenAI spend against Claude's equivalent models.

Frequently asked questions

Is the Claude API compatible with the OpenAI API format?
No — the Anthropic API is not a drop-in replacement at the HTTP level. The message format, authentication headers, and model IDs are different. However, a thin wrapper class can map the OpenAI `client.chat.completions.create()` interface to Claude's `client.messages.create()`, letting you migrate one call site at a time.
What is the Claude equivalent of GPT-4o?
`claude-sonnet-4-6` is the best general-purpose model in 2026 — comparable quality to GPT-4o at a lower price per token. Use `claude-haiku-4-5-20251001` where you used `gpt-4o-mini`. Use `claude-opus-4-7` where you needed the heaviest GPT-4-class reasoning.
How do I migrate system prompts from OpenAI to Claude?
OpenAI passes the system prompt as a message with `role: 'system'` in the messages array. Claude takes it as a separate `system=` parameter in `messages.create()`. Move it out of the messages list and pass it directly.
Does Claude support function calling / tool use?
Yes. Claude's tool_use API is semantically equivalent to OpenAI's function calling. The JSON schema for tools is identical; the difference is the key names: Claude uses `tools=` and returns `tool_use` blocks instead of `function_call` objects.
What happens to my tiktoken token counts when I switch to Claude?
Claude uses its own tokenizer. Token counts will differ slightly — typically 5–15% — for the same prompt. Use `client.messages.count_tokens()` (beta) for pre-flight estimates on Claude, or budget 15% more tokens than tiktoken reported.

Free tools

Cost Calculator → API Cookbook → Diff Summarizer → Skills Browser →

More examples

Claude API Python QuickstartClaude API Node.js / TypeScript QuickstartClaude API Streaming in PythonClaude API Streaming in Node.js / TypeScriptClaude API Tool Use in PythonClaude API Tool Use in Node.js / TypeScript