When you wire LLM calls into a shell script or CI job, the difference between a curl openai vs anthropic request body is more than auth headers. The two providers shape messages, system prompts, and streaming flags differently, and those differences leak into your error handling and retry logic.
Authentication and endpoint basics
OpenAI expects a bearer token in Authorization and posts to /v1/chat/completions. Anthropic uses x-api-key and anthropic-version headers, posting to /v1/messages. Neither accepts the other’s shape.
# OpenAI
curl https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-4o-mini",
"messages": [{"role":"user","content":"Say hi."}],
"max_tokens": 50
}'
# Anthropic
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-3-5-sonnet-20241022",
"max_tokens": 50,
"messages": [{"role":"user","content":"Say hi."}]
}'
The curl openai vs anthropic request body contrast starts with required fields: Anthropic mandates max_tokens at the top level; OpenAI treats it as optional but you should set it.
System prompts
OpenAI nests system inside messages with role:"system". Anthropic pulls it out to a top-level system string (or array). That means you cannot copy a messages array verbatim between the two.
// OpenAI
{"messages":[{"role":"system","content":"You are terse."},{"role":"user","content":"Hi"}]}
// Anthropic
{"system":"You are terse","messages":[{"role":"user","content":"Hi"}]}
Message content and multimodal
OpenAI accepts content as either a string or an array of parts. A vision request looks like:
{
"messages": [{
"role": "user",
"content": [
{"type":"text","text":"Describe"},
{"type":"image_url","image_url":{"url":"https://example.com/cat.jpg"}}
]
}]
}
Anthropic always uses a content array of typed blocks:
{
"messages": [{
"role": "user",
"content": [
{"type":"text","text":"Describe"},
{"type":"image","source":{"type":"url","url":"https://example.com/cat.jpg"}}
]
}]
}
If you build bodies programmatically, you need separate serializers.
Capabilities
Both support tool/function calling, vision, and streaming, but the wire format diverges. OpenAI attaches tools and tool_calls inside message objects. Anthropic uses tools at top level and returns tool_use blocks inside content arrays.
// OpenAI tool def
{"tools":[{"type":"function","function":{"name":"get_weather","parameters":{"type":"object"}}}]}
// Anthropic tool def
{"tools":[{"name":"get_weather","input_schema":{"type":"object"}}]}
Streaming: OpenAI sends SSE with data: {json} lines ending with data: [DONE]. Anthropic streams SSE with event types message_start, content_block_delta, and message_stop. Your parser must branch on provider.
# OpenAI stream
curl -N https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hi"}],"stream":true}'
# Anthropic stream
curl -N https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-3-5-sonnet-20241022","max_tokens":50,"messages":[{"role":"user","content":"Hi"}],"stream":true}'
Price and cost model
Both meter by token: separate input and output rates, billed per 1K or 1M tokens. Anthropic introduces prompt caching via cache_control on content blocks, giving discounted reuse of long system prompts. OpenAI offers cached token pricing on supported models with prompt_cache semantics in the response. Without quoting numbers, expect similar order-of-magnitude costs; the caching discount can matter for repetitive long contexts.
A minimal Anthropic cache hint:
{"system":[{"type":"text","text":"Long context..."},{"type":"text","text":"More","cache_control":{"type":"ephemeral"}}]}
OpenAI echoes prompt_tokens_details.cached_tokens when a hit occurs.
Latency and throughput
Empirically, both return first token in hundreds of milliseconds to seconds depending on model and load. Streaming helps perceived latency equally. Anthropic’s requirement to specify max_tokens prevents unbounded generation; OpenAI’s default (if omitted) may cap at a low server limit, causing silent truncation. For bulk curl jobs, set max_tokens explicitly on both.
Throughput on long generations is model-bound, not API-bound. Use stream to avoid client timeouts in shell scripts.
Ergonomics
The curl openai vs anthropic request body ergonomics favor OpenAI if you already speak chat-completions: one schema, many models, widespread examples. Anthropic’s split system field and version header add friction but make the system prompt explicit. A gateway like n4n.ai exposes one OpenAI-compatible endpoint that addresses 240+ models and handles fallback when a provider is degraded, letting you keep the OpenAI body shape while routing to either backend.
Error shapes
OpenAI returns {error:{message,type,code}} with HTTP status. Anthropic returns {type:"error",error:{type,message}}. Your shell script’s jq paths differ slightly.
# OpenAI error check
jq -e '.error' && echo "fail"
# Anthropic error check
jq -e '.type=="error"' && echo "fail"
Ecosystem
OpenAI’s API has years of community curl snippets, SDKs in every language, and middleware expecting its schema. Anthropic’s SDKs are official and clean but less ubiquitous. If you script with curl directly, ecosystem shows up as how many StackOverflow answers match your typo. Both publish OpenAPI specs; Anthropic’s includes the version header as a required parameter.
Limits
Context windows are model-specific; both enforce per-org rate limits returned in response headers (x-ratelimit-* for OpenAI, anthropic-ratelimit-* for Anthropic). Anthropic requires max_tokens <= model limit; OpenAI may reject if exceeds. Neither allows missing model. Anthropic rejects requests without anthropic-version; OpenAI ignores unknown headers.
Comparison table
| Dimension | OpenAI | Anthropic |
|---|---|---|
| Auth | Authorization: Bearer |
x-api-key + anthropic-version |
| System prompt | In messages as role:system |
Top-level system field |
| Required fields | model, messages |
model, messages, max_tokens |
| Streaming SSE | data: JSON, [DONE] |
Named events message_start etc. |
| Tool use | tools in body, tool_calls in msg |
tools top-level, tool_use blocks |
| Cost model | Per-token in/out, cached discounts | Per-token in/out, prompt caching |
| Latency | Similar, streaming recommended | Similar, max_tokens mandatory |
| Error body | {error:{message,type}} |
{type:"error",error:{...}} |
| Ecosystem | Larger community | Smaller but official SDKs |
Which to choose
Use OpenAI-shaped requests when: You maintain existing chat-completion code, need maximum community examples, or want one body to hit many model variants. The curl openai vs anthropic request body conversion cost is low if you stay in OpenAI land.
Use Anthropic-shaped requests when: You rely on long context with prompt caching, prefer explicit system separation, or target Claude-specific features like extended thinking (where supported). Write a small wrapper to inject anthropic-version and hoist system.
Use a unified gateway when: You want fallback across providers without maintaining two curl templates. Keeping the OpenAI body and routing via a compatible endpoint reduces surface area; the gateway forwards cache-control hints and meters per-token usage.
Pick based on where your code lives today. If it’s a throwaway shell script, OpenAI’s schema is less typing. If it’s a production pipeline with tight cost control, Anthropic’s caching and explicit limits earn their verbosity.