OpenAI API vs Anthropic API: the format differences and what to pick for your code
Two request formats solve the same task but differ in details that break code when porting. A comparison of structure, tools, streaming, cache and headers.
6 min · · API keys · сравнение
Both formats send messages to a model and get an answer back, so they look interchangeable. But when porting code from one to the other, details surface: where system lives, whether max_tokens is required, what the response looks like and how to read tool calls. Below is an OpenAI API vs Anthropic API breakdown: a comparison of /v1/chat/completions and /v1/messages, code examples for each and an explanation of which format is more economical through a gateway. On Zukko any model can be called in either format, so the choice is determined by your code and spend, not by whose model it is.
Quick comparison
| What | OpenAI: /v1/chat/completions | Anthropic: /v1/messages |
|---|---|---|
| Key header | Authorization: Bearer | x-api-key (the gateway accepts Bearer too) |
| API version | not needed | anthropic-version: 2023-06-01 (the SDK sets it) |
| System instruction | a message with role system in messages | a separate top-level system field |
max_tokens | optional | required |
| Response | choices[0].message.content | a list of content blocks, text in content[0].text |
| Token usage | usage.prompt_tokens, completion_tokens | usage.input_tokens, output_tokens and cache fields |
| Tools | tools with type: "function", reply in tool_calls | tools with input_schema, reply as a tool_use block |
| Streaming | delta chunks, ending with [DONE] | typed events (message_start, content_block_delta, message_stop) |
| Cache for Claude | no breakdown: all input at the input price | yes: cache read and write are accounted separately |
| Base URL in the SDK | https://api.zukko.pro/v1 | https://api.zukko.pro (the SDK adds /v1) |
Now point by point.
Request and response structure
OpenAI format
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.zukko.pro/v1", api_key=os.environ["ZUKKO_API_KEY"])
resp = client.chat.completions.create(
model="claude-sonnet-5-5",
messages=[
{"role": "system", "content": "Answer briefly."},
{"role": "user", "content": "What is a token?"},
],
max_tokens=300,
)
print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)Anthropic format
import os
import anthropic
client = anthropic.Anthropic(
base_url="https://api.zukko.pro", # no /v1: the SDK adds it
api_key=os.environ["ZUKKO_API_KEY"],
)
resp = client.messages.create(
model="claude-sonnet-5-5",
system="Answer briefly.",
messages=[{"role": "user", "content": "What is a token?"}],
max_tokens=300,
)
print(resp.content[0].text)
print(resp.usage.input_tokens, resp.usage.output_tokens)Notable differences: system is moved out of the message list, max_tokens is mandatory, and the response arrives as an array of blocks. There may be several blocks (for example text and then a tool call), so in real code select blocks with type == "text" rather than taking the first one.
For curl requests and field descriptions see the documentation: chat/completions and messages.
Tools (function calling)
In the OpenAI format a tool is described with a function wrapper, and the model returns the call in message.tool_calls; arguments arrive as a JSON string in function.arguments that you must parse, and the result goes back as a message with role tool and the tool_call_id:
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Weather in a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
},
}]In the Anthropic format the schema lives in input_schema, the call arrives as a tool_use block with an already parsed input object, and the result goes back as a user message with a tool_result block (carrying tool_use_id):
tools = [{
"name": "get_weather",
"description": "Weather in a city",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]},
}]The full loop (call, run the function, return the result) takes roughly the same amount of code in both formats; only the field names differ.
Streaming
In the OpenAI format the server sends chunks with choices[0].delta, and at the end of the stream comes a chunk with usage and empty choices. So always check if chunk.choices: before accessing chunk.choices[0]:
stream = client.chat.completions.create(
model="claude-sonnet-5-5",
messages=[{"role": "user", "content": "Tell me about prompt caching"}],
stream=True,
max_tokens=500,
)
for chunk in stream:
if chunk.choices:
print(chunk.choices[0].delta.content or "", end="")In the Anthropic SDK a stream manager is more convenient:
with client.messages.stream(
model="claude-sonnet-5-5",
messages=[{"role": "user", "content": "Tell me about prompt caching"}],
max_tokens=500,
) as stream:
for text in stream.text_stream:
print(text, end="")Under the hood these are typed events, and you do not have to parse them yourself. The event format is described in the streaming section of the documentation.
Cache and cost: the main practical difference
Here the format affects money. By Zukko's rules:
- for Claude in the Anthropic format the gateway sees cache reads and writes (the
cache_read_input_tokensandcache_creation_input_tokensfields) and bills them at their own prices; - for Claude in the OpenAI format there is no cache breakdown, and all input is billed at the input price;
- for GPT the cache read is visible in both formats (
prompt_tokens_details.cached_tokens).
An example. An agent step on claude-sonnet-5-5 (input $0.4, cache read $0.05 per 1M tokens, prices as of October 18, 2026) with a 50,000-token prefix that is fully read from the cache. We count only this prefix, without output and new tokens:
/v1/messages: 50,000 × 0.05 / 1,000,000 = $0.0025 per request;/v1/chat/completions: 50,000 × 0.4 / 1,000,000 = $0.02 per request.
Over 100 requests that is $0.25 versus $2.00. This is an upper bound that assumes the cache hit on every request (the first cache write is billed separately, $0.5 per 1M for Sonnet 5.5). But the direction is clear: for long repeated prefixes (agents, chat with a big system prompt) it is more economical to call Claude through the Anthropic format. More in Claude via /v1/messages or /v1/chat/completions: what happens to the cache.
For the cache to work in the Anthropic format, the client must mark the cacheable part. Claude Code and many agents do it themselves. In your own code it looks like this:
resp = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=300,
system=[{
"type": "text",
"text": LONG_INSTRUCTION, # a long stable prefix
"cache_control": {"type": "ephemeral"}, # cache marker
}],
messages=[{"role": "user", "content": "A question about the instruction"}],
)
print(resp.usage.cache_creation_input_tokens, resp.usage.cache_read_input_tokens)The minimum length of a cacheable prefix depends on the model; see Anthropic's documentation. The general formula: fresh input × input price + cache reads × read price + cache writes × write price + output × output price.
Other models through either format
Through the gateway models are not tied to "their own" format. GPT, Gemini, DeepSeek, Qwen and GLM can be called through both /v1/chat/completions and /v1/messages: the gateway translates the request. For example, glm-5.3-flash works in the Anthropic format, and the gateway converts the request and response itself. But models have different nuances: not all of them support the same set of parameters, so check behavior in your own code. The current model list and prices: GET https://api.zukko.pro/v1/models or the Models page in the dashboard.
What to choose
| Situation | Format |
|---|---|
| You already have OpenAI SDK code, many models from different vendors | /v1/chat/completions |
| The main model is Claude, long repeated prefixes, agents | /v1/messages |
| Claude Code, the Anthropic SDK, any client in "Anthropic mode" | /v1/messages (the client does it) |
| Cursor, Cline in OpenAI mode, LangChain | /v1/chat/completions |
| A new project with no preference | OpenAI format as the most widespread; Anthropic for Claude with cache |
If you pick a provider in an IDE, use the Choose provider page in the Zukko docs: Anthropic with the address https://api.zukko.pro or OpenAI-compatible with /v1. The key is the same in both cases. A third endpoint, /v1/responses, is also available, but that is a separate topic.
Switching from one to the other: a checklist
- Change the base URL: with
/v1for the OpenAI SDK, without/v1for the Anthropic SDK. - Move
systemout ofmessages, or back. - Add
max_tokensfor the Anthropic format. - Rework response parsing and the
usagefields. - Rewrite tool descriptions and result handling.
- Check streaming, including the last chunk with
usage. - Run tests on your own prompts and compare spend in the dashboard.
One key works for both formats; you create it in the Zukko dashboard.
FAQ
Can I call Claude through the OpenAI SDK?
Yes, through /v1/chat/completions with the address https://api.zukko.pro/v1. It works, but the Claude cache is not broken down in this format and all input is billed at the input price.
Can I call GPT through the Anthropic SDK?
The gateway lets you call any model in any format, but check the behavior of the specific model in your code. For GPT the OpenAI format is simpler and more familiar.
How do the authorization headers differ?
OpenAI uses Authorization: Bearer, Anthropic uses x-api-key and anthropic-version. The Zukko gateway accepts the key in either header.
Why is max_tokens required in the Anthropic format?
That is how the Anthropic format is designed: the field is mandatory. In the OpenAI format you can omit it, but to control spend we recommend setting a limit in both.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- Gemini API vs Vertex API: the difference and which one to pickGoogle offers two doors to Gemini: an AI Studio key or a Google Cloud project. We compare them and show a third route through a gateway.
- OpenRouter: what the service is and who it suits in 2026OpenRouter offers one API and one balance for many models from different providers. We cover how it works, its fees and limits, and when another gateway is more convenient.
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.