OpenAI vs Anthropic: A Developer's Guide to Chat Completions and Messages APIs

Apr 22, 2026 at 5:00:00 AM

If you're building an application that talks to a large language model, chances are you'll be integrating with either OpenAI's /v1/chat/completions or Anthropic's /v1/messages endpoint. While both serve the same fundamental purpose—sending a conversation to an LLM and getting a response—they differ in meaningful ways across authentication, request structure, response format, tool use, streaming, and more.

This guide covers every major difference so you can make informed decisions when choosing between them or building an abstraction layer that supports both.

Authentication

AspectOpenAIAnthropic
Auth headerAuthorization: Bearer sk-...x-api-key: sk-ant-...
Version headerNone requiredanthropic-version: 2023-06-01 (mandatory)
Org/project headersOpenAI-Organization, OpenAI-Project (optional)N/A

Anthropic's mandatory anthropic-version header pins the API behavior to a specific version, decoupling API versioning from model names. OpenAI versions through model names and endpoint changes instead.

System Prompts

This is one of the most visible architectural differences.

OpenAI places the system prompt inside the messages array:

{
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Hello"}
  ]
}

Anthropic uses a dedicated top-level system parameter, separate from the messages array:

{
  "system": "You are a helpful assistant.",
  "messages": [
    {"role": "user", "content": "Hello"}
  ]
}

Anthropic's system field also accepts an array of content blocks (not just a string), which enables features like prompt caching on the system prompt via cache_control.

Message Roles and Ordering

OpenAIAnthropic
system / developer (o-series)N/A (system is top-level)
useruser
assistantassistant
tool (for tool results)N/A (tool results go inside user messages)

OpenAI has 4 distinct roles. Anthropic has only 2 (user and assistant). Anthropic strictly requires alternating user/assistant messages—you cannot have two consecutive messages of the same role. OpenAI is more flexible and will concatenate consecutive same-role messages.

Response Format

OpenAI wraps the response in a choices array (supporting the n parameter for multiple completions):

{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "Hello!"},
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 13,
    "completion_tokens": 7,
    "total_tokens": 20
  }
}

Anthropic returns content directly as an array of typed content blocks:

{
  "id": "msg_abc123",
  "type": "message",
  "role": "assistant",
  "content": [
    {"type": "text", "text": "Hello!"}
  ],
  "stop_reason": "end_turn",
  "usage": {
    "input_tokens": 13,
    "output_tokens": 7,
    "cache_creation_input_tokens": 0,
    "cache_read_input_tokens": 0
  }
}
AspectOpenAIAnthropic
Content typemessage.content is a stringcontent is an array of typed blocks
Stop indicatorfinish_reason: "stop"stop_reason: "end_turn"
Length stop"length""max_tokens"
Tool call stop"tool_calls""tool_use"
Multiple completionsYes (via n parameter)No (always returns 1)
Total tokensProvidedMust be calculated
Cache statsNot in responseBuilt-in (cache_creation_input_tokens, cache_read_input_tokens)

Key Parameters

ParameterOpenAIAnthropic
Max output tokensmax_completion_tokens (optional)max_tokens (required)
Temperature0–2 (default 1)0–1 (default 1)
Top Ptop_ptop_p
Top KNot availabletop_k
Frequency penaltyfrequency_penalty (-2 to 2)Not available
Presence penaltypresence_penalty (-2 to 2)Not available
Stop sequencesstop (string or array)stop_sequences (array)
Seed (reproducibility)seedNot available
Log probabilitieslogprobsNot available
User IDusermetadata.user_id
Extended thinkingN/A (o-series reason internally)thinking object with budget_tokens

Two things often trip up developers migrating between them: Anthropic requires max_tokens in every request (OpenAI defaults to the model maximum), and Anthropic's temperature range caps at 1.0 while OpenAI goes up to 2.0.

Tool Use / Function Calling

This is one of the largest architectural divergences between the two APIs.

Tool Definition

OpenAI wraps tools in a type/function structure:

{
  "tools": [{
    "type": "function",
    "function": {
      "name": "get_weather",
      "description": "Get weather for a location",
      "parameters": {
        "type": "object",
        "properties": {"location": {"type": "string"}},
        "required": ["location"]
      }
    }
  }]
}

Anthropic uses a flatter structure with input_schema:

{
  "tools": [{
    "name": "get_weather",
    "description": "Get weather for a location",
    "input_schema": {
      "type": "object",
      "properties": {"location": {"type": "string"}},
      "required": ["location"]
    }
  }]
}

Tool Calls in Response

OpenAI returns tool calls on a separate tool_calls array with arguments as a JSON string that must be parsed:

"tool_calls": [{
  "id": "call_abc123",
  "type": "function",
  "function": {
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris\"}"
  }
}]

Anthropic returns tool calls as content blocks with input as a parsed JSON object:

"content": [{
  "type": "tool_use",
  "id": "toolu_01D7FLrfh4GYq7yT1ULFeyMV",
  "name": "get_weather",
  "input": {"location": "Paris"}
}]

Returning Tool Results

OpenAI uses a dedicated tool role:

{"role": "tool", "tool_call_id": "call_abc123", "content": "Sunny, 22C"}

Anthropic places tool results as content blocks inside a user message, with an explicit is_error flag:

{
  "role": "user",
  "content": [{
    "type": "tool_result",
    "tool_use_id": "toolu_01D7...",
    "content": "Sunny, 22C",
    "is_error": false
  }]
}

Tool Choice

BehaviorOpenAIAnthropic
Model decides"auto"{"type": "auto"}
Must use a tool"required"{"type": "any"}
Specific tool{"type": "function", "name": "X"}{"type": "tool", "name": "X"}
No tools"none"{"type": "none"}

Vision / Multimodal

OpenAI uses the data URL scheme for base64 images:

{
  "type": "image_url",
  "image_url": {
    "url": "data:image/jpeg;base64,{BASE64_DATA}",
    "detail": "high"
  }
}

Anthropic uses separate fields for media type and data:

{
  "type": "image",
  "source": {
    "type": "base64",
    "media_type": "image/jpeg",
    "data": "BASE64_DATA"
  }
}
AspectOpenAIAnthropic
Detail controldetail: "high"/"low"/"auto"None
PDF supportNot in Chat CompletionsNative document content block
Audio supportYesNo

Anthropic has a unique first-class document block type for PDFs and text files with optional citation support—a feature OpenAI's Chat Completions endpoint doesn't offer.

Streaming

Both APIs use Server-Sent Events, but with fundamentally different structures.

OpenAI uses a flat stream of unnamed data: lines, ending with data: [DONE]:

data: {"choices":[{"delta":{"content":"Hello"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]

Anthropic uses named event types with a structured lifecycle:

event: message_start
data: {"type":"message_start","message":{...}}

event: content_block_start
data: {"type":"content_block_start","index":0,...}

event: content_block_delta
data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Hello"}}

event: content_block_stop
data: {"type":"content_block_stop","index":0}

event: message_stop
data: {"type":"message_stop"}

Anthropic's streaming is more granular with 6+ named event types covering the full message lifecycle. This makes mixed content (text interleaved with tool calls) easier to handle but adds parsing complexity. OpenAI's approach is simpler—essentially one event type plus a sentinel.

Error Handling

OpenAI includes a param field indicating which parameter caused the error:

{
  "error": {
    "message": "Incorrect API key",
    "type": "invalid_request_error",
    "param": null,
    "code": "invalid_api_key"
  }
}

Anthropic uses a type-based discrimination pattern:

{
  "type": "error",
  "error": {
    "type": "authentication_error",
    "message": "Invalid API key"
  }
}

Anthropic distinguishes overloaded_error from api_error, making it easier to implement backoff logic specifically for capacity issues.

Rate Limiting

AspectOpenAIAnthropic
Header prefixx-ratelimit- (lowercase)RateLimit- (IETF draft format)
Reset formatRelative duration (1s, 6m0s)ISO 8601 timestamp
Retry headerNot standardRetry-After

Both return HTTP 429 for rate limit errors and recommend exponential backoff with jitter.

Unique Features

OpenAI-only

  • Multiple completions — the n parameter generates N alternative responses per request
  • Log probabilitieslogprobs returns token-level probability information
  • Structured outputresponse_format with json_schema for guaranteed JSON structure
  • Frequency/presence penalties for controlling repetition
  • Audio input/output support in multimodal messages
  • Seed parameter for reproducible outputs

Anthropic-only

  • Extended thinking — explicit thinking parameter with budget_tokens, returns visible thinking blocks
  • Prompt cachingcache_control on content blocks with TTL options, with cache hit/miss reporting in usage
  • PDF/document processing — native document content blocks with citation support
  • Top K samplingtop_k parameter for controlling token selection
  • Built-in server toolsweb_search, code_execution, text_editor, etc. that run on Anthropic's infrastructure
  • Tool error flagis_error field on tool results

SDK Quick Reference

# OpenAI
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.choices[0].message.content)

# Anthropic
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
    model="claude-sonnet-4-5-20250514",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello"}]
)
print(response.content[0].text)

Migration Checklist

If you're switching between the two or building a unified abstraction, here are the key things to watch for:

  1. Move system prompts — from inside messages (OpenAI) to the top-level system field (Anthropic), or vice versa
  2. Set max_tokens — it's required in Anthropic, optional in OpenAI
  3. Clamp temperature — Anthropic caps at 1.0; OpenAI allows up to 2.0
  4. Restructure tool definitionsparameters vs input_schema, wrapper object differences
  5. Handle tool results differentlytool role (OpenAI) vs content blocks in user message (Anthropic)
  6. Parse tool call arguments — JSON string (OpenAI) vs parsed object (Anthropic)
  7. Enforce message alternation — required for Anthropic, flexible in OpenAI
  8. Update auth headersAuthorization: Bearer vs x-api-key + anthropic-version
  9. Adapt streaming parsers — flat chunks vs named lifecycle events
  10. Unwrap responseschoices[0].message.content vs content[0].text

Both APIs are powerful and well-designed, but they reflect different philosophies. OpenAI's Chat Completions API leans toward flexibility and backwards compatibility, while Anthropic's Messages API favors explicitness and structured data. Understanding these differences will help you build robust integrations regardless of which provider you choose.