Back to blog
Guide·May 23, 2026·6 min read

Calling Claude with the OpenAI SDK — change one line, keep your codebase

Anthropic's SDK is great, but the ecosystem standardized on OpenAI's. Here's how to call Claude Opus 4.7, Sonnet 4.6 and Haiku 4.5 with the unmodified OpenAI Python and Node SDKs — streaming, tool use, vision included.

Anthropic's own SDK is great, but the entire AI ecosystem has standardized on the OpenAI client shape — every example, every framework adapter, every "hello world" tutorial assumes you have an OpenAI(...) instance handy. Migrating your code to call Anthropic's SDK directly is a meaningful refactor.

It doesn't have to be. This post shows how to call Claude Opus 4.7, Sonnet 4.6 and Haiku 4.5 through the OpenAI SDK unchanged — by routing your calls through Kunavo. Same SDK, same types, same streaming, same tool use. The only line that changes is base_url.

The minimum change

With Python's openai package already installed, the full migration looks like this.

main.py
from openai import OpenAI

client = OpenAI(
    api_key="sk-kn-...",
    base_url="https://api.kunavo.com/v1",   # the only line that changes
)

resp = client.chat.completions.create(
    model="claude-sonnet-4-6",              # a Claude slug, not gpt-4o
    messages=[
        {"role": "system", "content": "You are a senior platform engineer."},
        {"role": "user",   "content": "Critique this SQL migration..."},
    ],
)
print(resp.choices[0].message.content)

api_key becomes your Kunavo key (created in /app/keys). base_url points at our gateway. The model id switches from gpt-4o to a Claude slug — claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-5. The request body, the response shape, and every helper in the SDK behave exactly as they do against OpenAI.

Streaming

Streaming works identically. Kunavo forwards Anthropic's SSE chunks in OpenAI's chat.completion.chunk format, so the existing async iteration pattern works without modification.

stream.py
for chunk in client.chat.completions.create(
    model="claude-opus-4-7",
    messages=[{"role": "user", "content": "Explain Raft in 200 words."}],
    stream=True,
):
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

Tool use / function calling

Anthropic's tool use protocol is semantically the same as OpenAI's function calling — they differ only at the wire level. Kunavo translates the tools array, the response tool_calls, and the follow-up tool messages in both directions. Use tool_choice="auto", "none", or a named tool — they all map.

tools.py
tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Get current weather in a city",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string"},
                    "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
                },
                "required": ["city"],
            },
        },
    }
]

resp = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools,
    tool_choice="auto",
)
print(resp.choices[0].message.tool_calls)

Vision

Claude has been multimodal since 3.5; passing an image is the standard OpenAI content: [{ type: 'text' }, { type: 'image_url' }] array. image_url.url can be an https URL or a data: base64 URI.

vision.py
resp = client.chat.completions.create(
    model="claude-sonnet-4-6",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What's in this image?"},
            {"type": "image_url",
             "image_url": {"url": "https://example.com/cat.jpg"}},
        ],
    }],
)

When you do want the native Anthropic SDK

Some Anthropic-only features can't be expressed in the OpenAI shape — most importantly the cache_control directive for prompt caching, and extended thinking tokens. If you need either, switch SDKs but keep the same key: Kunavo also exposes the native /v1/messages endpoint, so Anthropic's SDK works with a single base_url change too.

anthropic_native.py
from anthropic import Anthropic

client = Anthropic(
    api_key="sk-kn-...",
    base_url="https://api.kunavo.com",     # SDK appends /v1/messages
)

resp = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    system="You are a senior platform engineer.",
    messages=[{"role": "user", "content": "What is a hot standby?"}],
)
print(resp.content[0].text)

See the native Messages API docs for the full list of pass-through parameters, and /docs/caching for how prompt caching saves up to 90% of input cost on repeat prompts.

Prompt caching itself does not require switching SDKs. On a long prompt Kunavo sets the cache breakpoints for you on the OpenAI-shape route: on the system prompt, on the tool definitions and on the last message of a conversation that already contains an assistant turn. A cache_control you put on a system message, a user message or a tool definition yourself is passed on to Claude.

What you give up — and what you gain

The OpenAI-shape route is a translation, not native protocol. Two small things don't cross the boundary:

  • Thinking controls — thinking and reasoning_effort are not forwarded to Claude on this route. To turn extended thinking on or adjust it, use the native Messages API.
  • Thinking output — when the model thinks, the response does not include that thinking, and the usage object has no separate count for it: the thinking tokens are counted inside completion_tokens.

What you gain is significant: one SDK across Claude, GPT, GPT-Image, Veo and the rest of the catalog; under upstream official pricing depending on the model; Stripe-native billing in your local currency; no silent model swaps — failover changes the vendor, never the model, and the dashboard itemises every call's model, tokens and cost.

Two minutes to confirm

Sign up at kunavo.com/app/signup — a $10 top-up covers several thousand Claude calls, pay-as-you-go, and your balance never expires. Drop your base_url in, swap your model id to a Claude slug, run your existing test suite against it. If something doesn't cross cleanly, email contact@kunavo.com — we read every message.