In short:
- Both formats send a list of chat messages and get text back, so simple prompts port easily. The differences show up in system prompts,
max_tokens, tool calling, streaming events and usage fields. - Your tool usually decides the format for you: Claude Code speaks the Anthropic format; Codex CLI, Cursor, Cline and most other agents speak the OpenAI format.
- For your own code, choose the format whose SDK you already use. Choose the native Anthropic format when you need Claude-specific features such as explicit prompt-cache breakpoints.
- On Relaykit, the same key works with both:
https://api.oneaiapikey.com/v1for OpenAI format,https://api.oneaiapikey.comfor Anthropic format.
“OpenAI-compatible” has become the default promise of almost every LLM provider and gateway. Anthropic’s Messages API is the other format you will meet often, mainly because Claude Code and the Claude Agent SDK use it. This post explains, field by field, where the two actually differ, so you can choose with confidence and debug faster when a request that worked in one format fails in the other.
The same request, two ways
Here is the same one-question request in each format, using the official Python SDKs pointed at our gateway. Both use the same key and the same model.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.oneaiapikey.com/v1",
api_key=os.environ["LLM_API_KEY"],
)
resp = client.chat.completions.create(
model="claude-sonnet-5",
max_tokens=200,
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Explain idempotency in one sentence."},
],
)
print(resp.choices[0].message.content)
print(resp.choices[0].finish_reason) # "stop", "length", "tool_calls"
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)import os
import anthropic
client = anthropic.Anthropic(
base_url="https://api.oneaiapikey.com", # no /v1: the SDK adds /v1/messages
api_key=os.environ["LLM_API_KEY"],
)
msg = client.messages.create(
model="claude-sonnet-5",
max_tokens=200, # required in this format
system="You are a concise assistant.", # top-level, not a message
messages=[
{"role": "user", "content": "Explain idempotency in one sentence."},
],
)
print(msg.content[0].text)
print(msg.stop_reason) # "end_turn", "max_tokens", "tool_use"
print(msg.usage.input_tokens, msg.usage.output_tokens)They look nearly identical, and for plain chat they are. Now the details.
Differences at a glance
| Topic | OpenAI format | Anthropic format |
|---|---|---|
| Endpoint | POST /v1/chat/completions (also /v1/responses) |
POST /v1/messages |
| Auth header | Authorization: Bearer sk-... |
x-api-key: sk-... (gateways often also accept Bearer) |
| Version header | none | anthropic-version: 2023-06-01 |
| System prompt | a message with role system (or developer) |
top-level system field |
max_tokens |
optional | required |
| Response text | choices[0].message.content |
content array of blocks, e.g. content[0].text |
| Stop signal | finish_reason: stop, length, tool_calls |
stop_reason: end_turn, max_tokens, tool_use, stop_sequence |
| Usage | prompt_tokens, completion_tokens |
input_tokens, output_tokens, plus cache read and write counts |
| Tool call arguments | JSON string in arguments |
JSON object in input |
| Streaming | data: chunks, ends with data: [DONE] |
named events: message_start, content_block_delta, message_stop and others |
| Prompt caching | automatic on supported models | explicit cache_control breakpoints |
System prompts
In the OpenAI format, the system prompt is just the first message, with role system (newer OpenAI models also accept developer). In the Anthropic format, it is a separate top-level system field, which can be a string or a list of text blocks. Putting a system role inside messages in an Anthropic request is one of the most common porting errors; the native API only accepts user and assistant there.
This matters more than it seems. Because the Anthropic system field can be a list of blocks, you can attach a cache breakpoint to a long system prompt and pay the cached-input rate when it is reused. More on that in our cost estimation guide.
max_tokens and why “required” is a good thing
The Anthropic format requires max_tokens on every request. The OpenAI format lets you omit it, and the model then writes until it stops naturally or reaches the model’s own output limit.
Setting it explicitly in both formats is a good habit. Output tokens are the expensive side of every model’s price: on our current catalogue, output costs at least 2× as much as input for every model (for example, Claude Sonnet 5 is $2.20 per million input tokens and $11 per million output). A sensible cap protects you from a runaway response. When the cap is hit you get finish_reason: "length" or stop_reason: "max_tokens", which your code should treat as “the answer may be truncated”, not as success.
Content: strings vs blocks
OpenAI-format message content is usually a string, or a list of parts when you send images. Anthropic-format content is always conceptually a list of blocks: text, image, tool_use, tool_result, thinking and so on. The response is also a list of blocks, so msg.content[0].text works for simple answers but will break when the first block is a thinking or tool-use block. Robust code loops over blocks and joins the ones with type == "text".
Tool calling
Both formats support function calling, but the shapes differ enough that a hand-written adapter needs care.
Defining a tool. OpenAI wraps the schema in a function object and calls it parameters. Anthropic puts the name at the top level and calls the schema input_schema:
// OpenAI format
{ "type": "function",
"function": { "name": "get_weather",
"parameters": { "type": "object", "properties": { "city": { "type": "string" } } } } }
// Anthropic format
{ "name": "get_weather",
"input_schema": { "type": "object", "properties": { "city": { "type": "string" } } } }
Receiving a call. OpenAI returns message.tool_calls, each with function.arguments as a JSON-encoded string that you must parse. Anthropic returns a tool_use content block whose input is already an object.
Returning the result. OpenAI expects a new message with role tool and the matching tool_call_id. Anthropic expects a user message containing a tool_result block with the matching tool_use_id.
If you only ever use one SDK, none of this matters. It matters when you switch formats, or when a tool’s “OpenAI-compatible” provider setting is used with a Claude model: the gateway translates between the two, which works well for standard tool calls but may not carry newer, format-specific features.
Streaming
Both formats stream over Server-Sent Events, but the events differ:
- OpenAI format: every event is an unnamed
data:line holding a chunk object. Text arrives inchoices[0].delta.content. The stream ends with a literaldata: [DONE]. Token usage is only included in the final chunk if you ask for it withstream_options: {"include_usage": true}. - Anthropic format: events are named. You get
message_start, then for each block acontent_block_start, severalcontent_block_deltaevents (text arrives astext_delta), and acontent_block_stop. Thenmessage_delta(carryingstop_reasonand output usage) andmessage_stop. There may bepingevents in between and anerrorevent if something goes wrong mid-stream.
The SDKs hide these differences, so prefer them over hand-written parsers. If you need to write one, our streaming guide has working parsers for both.
Usage and prompt caching
Usage fields are named differently (prompt_tokens vs input_tokens), and the Anthropic format reports cache activity separately as cache_creation_input_tokens and cache_read_input_tokens. In the OpenAI format, cached tokens appear under usage.prompt_tokens_details.cached_tokens on models that support it.
The bigger difference is control. OpenAI-format caching is automatic for long, repeated prompt prefixes on supported models. Anthropic-format caching is opt-in: you mark the end of a reusable prefix with "cache_control": {"type": "ephemeral"}. Explicit control is more work, but it is predictable, which is why agent tools that resend large contexts on every turn use it.
So when does the format actually matter?
1. When your tool only speaks one. This is the most common case and it decides itself:
- Claude Code uses the Anthropic format via
ANTHROPIC_BASE_URL. - Codex CLI, Cursor, Cline and most other editors and agents use the OpenAI format via a custom base URL.
2. When you need a model only one format exposes. On Relaykit, Claude models are available in both formats; the other families are OpenAI format only:
- OpenAI format (11 models): Claude Opus 5.5, Claude Sonnet 5, Claude Haiku 4.5, GPT-6.1 Sol, GPT-5.6 Sol, GPT-5.6 Terra, Grok 4.6, Claude Sonnet 5.5, Grok 4.7, DeepSeek V4 Flash, DeepSeek V4 Pro.
- Anthropic format (4 models): Claude Opus 5.5, Claude Sonnet 5, Claude Haiku 4.5, Claude Sonnet 5.5.
If you want to compare Claude with GPT or Grok in your own app using one code path, the OpenAI format is the practical choice.
3. When you rely on Claude-specific features. Explicit cache breakpoints, extended thinking blocks and other Claude features are first-class in the Messages API. Through a translation layer they may be simplified or missing. If you are building on Claude specifically, use the Anthropic SDK.
4. When you parse responses yourself. Any code that reads raw JSON, logs usage or bills customers per token must handle the field names in the table above. This is where most “it worked yesterday” bugs come from after switching formats.
Porting checklist
When moving code from one format to the other:
- Change the base URL: OpenAI SDKs need
/v1on the end; Anthropic SDKs must not have it. - Move the system prompt between
messagesand the top-levelsystemfield. - Add
max_tokensif you are moving to the Anthropic format. - Rewrite tool definitions, tool-call parsing (string vs object) and tool-result messages.
- Update the response reading code:
choices[0].message.contentvs looping overcontentblocks. - Update usage and stop-reason handling, including truncation checks.
- Re-test streaming; event names and the end-of-stream signal differ.
Next steps
- Try both formats in two minutes with the quickstart.
- Browse the model catalogue to see which formats each model supports, and the pricing page for how credits work.
- Read about using Claude Code with a custom base URL if Claude Code is your main tool.