Skip to content

Protocol Conversion Guide

RouteAPI, as a unified API gateway, automatically handles conversions between different protocol formats internally. When you call a Claude model using the OpenAI protocol, or call a Gemini model using the Claude Messages protocol, RouteAPI handles the differences in message structure, parameter mapping, and response format, so you don’t need to worry about underlying protocol details.

RouteAPI supports three mainstream protocol entry points:

  • OpenAI-compatible protocol: /v1/chat/completions, /v1/responses
  • Claude Messages protocol: /v1/messages
  • Google Gemini protocol: /v1beta/models/{model}:generateContent

When a request arrives, RouteAPI identifies the protocol type based on the path, determines the target upstream based on the model ID, and then performs necessary format conversions between them.

ScenarioConversion?Description
OpenAI protocol → OpenAI modelsNoPass-through
Claude Messages → Claude modelsNoPass-through
OpenAI protocol → Claude modelsYesOpenAI → Claude Messages
OpenAI protocol → Gemini modelsYesOpenAI → Gemini contents
Claude Messages → OpenAI modelsYesClaude Messages → OpenAI
Claude Messages → Gemini modelsYesClaude Messages → Gemini

Protocol conversion is transparent to clients. You send an OpenAI-format request and receive an OpenAI-format response, even if Claude or Gemini is called underneath.

However, conversion has limitations:

  • Parameters not supported by the target protocol will be ignored or use default values.
  • Some protocol-specific capabilities (like Claude’s extended thinking, prompt caching) may not be fully expressible after conversion.
  • Conversion introduces slight latency (typically under 10ms).

Best Practice: Prioritize using the protocol natively supported by the target model for the most complete feature support and best performance.

OpenAI places the system prompt as the first message in the messages array:

{
"messages": [
{ "role": "system", "content": "You are a rigorous technical assistant." },
{ "role": "user", "content": "Explain what an API gateway is" }
]
}

Claude places the system prompt in a top-level independent field:

{
"system": "You are a rigorous technical assistant.",
"messages": [
{ "role": "user", "content": "Explain what an API gateway is" }
]
}

Conversion Rules:

  • OpenAI → Claude: Extract the first role: "system" message and move it to the system field.
  • Claude → OpenAI: Convert the system field content to a role: "system" message and insert it at the beginning of the messages array.
OpenAIClaudeGeminiDescription
systemTop-level system fieldsystemInstructionSystem prompt location differs
useruseruserUser message, consistent
assistantassistantmodelAssistant/model reply, different names
tooltool_result within userfunctionResponse within userTool result attribution differs

Conversion Considerations:

  • Claude doesn’t accept two consecutive messages with the same role; conversion requires merging or inserting placeholder messages.
  • Gemini’s model role is mapped to assistant when converting to OpenAI.
  • OpenAI’s role: "tool" is merged into user messages in both Claude and Gemini.

Gemini’s message structure is called contents, with each message’s role called role and content in a parts array:

{
"contents": [
{
"role": "user",
"parts": [{ "text": "Explain what an API gateway is" }]
}
]
}

Conversion Rules:

  • OpenAI messages ↔ Gemini contents
  • OpenAI content ↔ Gemini parts
  • OpenAI assistant ↔ Gemini model
  • system prompt converts to top-level systemInstruction field
OpenAIClaudeGeminiDescription
temperaturetemperaturetemperatureClaude max 1, OpenAI max 2, Gemini max 2
top_ptop_ptopPnucleus sampling, all three support
Not supportedtop_ktopKOpenAI doesn’t support, dropped in conversion
max_tokens / max_completion_tokensmax_tokens (required)maxOutputTokensClaude requires explicit setting
nNot supportedcandidateCountClaude doesn’t support multiple candidates
stopstop_sequencesstopSequencesDifferent field names, same semantics

Temperature Range Conversion:

When an OpenAI request’s temperature exceeds 1 and the target is Claude, RouteAPI automatically truncates it to 1 to avoid upstream rejection.

OpenAIClaudeGeminiDescription
response_formatNot supportedresponseMimeTypeOpenAI supports JSON mode and JSON Schema
frequency_penaltyNot supportedfrequencyPenaltyClaude doesn’t support penalty parameters
presence_penaltyNot supportedpresencePenaltyClaude doesn’t support penalty parameters
streamstreamstreamAll three support, but event formats differ completely
stream_options.include_usageAlways returnedAuto-returned when generateContentRequest.stream=trueUsage stats return method differs

Conversion Behavior:

  • frequency_penalty and presence_penalty are ignored when forwarded to Claude.
  • response_format: { type: "json_object" } is simulated via tool calls when forwarded to Claude, or JSON output prompt added to system.
  • n > 1 is reset to 1 when forwarded to Claude, as Claude doesn’t support multi-candidate generation.
OpenAIClaudeGeminiDescription
usermetadata.user_idNot supportedFor abuse detection
seedNot supportedseedClaude doesn’t support deterministic sampling
logprobs / top_logprobsNot supportedNot supportedOnly OpenAI models support
Not supportedthinkingNot supportedClaude-specific extended thinking configuration
{
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Query current weather for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
}
]
}
{
"tools": [
{
"name": "get_weather",
"description": "Query current weather for a specified city",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
]
}
{
"tools": [
{
"functionDeclarations": [
{
"name": "get_weather",
"description": "Query current weather for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
]
}
]
}
OpenAIClaudeGeminiConversion Notes
tools[].type: "function"No such levelNo such levelRemove type wrapper in conversion
tools[].functionFlatten in tools[]Place in functionDeclarations[]Different hierarchy structure
function.parametersinput_schemaparametersClaude field name differs
OpenAIClaudeGeminiDescription
"auto"{ "type": "auto" }"AUTO"Model decides automatically
"none"{ "type": "none" }"NONE"Prohibit tool calls
"required"{ "type": "any" }"ANY"Must call a tool
{ "type": "function", "function": { "name": "get_weather" } }{ "type": "tool", "name": "get_weather" }{ "functionCallingConfig": { "allowedFunctionNames": ["get_weather"] } }Force specific tool, structure differs greatly
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "Beijing, sunny, 23°C"
}
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "Beijing, sunny, 23°C"
}
]
}
{
"role": "user",
"parts": [
{
"functionResponse": {
"name": "get_weather",
"response": { "result": "Beijing, sunny, 23°C" }
}
}
]
}

Conversion Key Points:

  • OpenAI’s role: "tool" is merged into user messages when converting to Claude/Gemini.
  • Tool call ID field names differ: tool_call_id vs tool_use_id vs Gemini’s function name identification.
  • Claude and Gemini require all tool results in the same user message; OpenAI allows separate tool messages.
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://example.com/image.jpg",
"detail": "high"
}
},
{ "type": "text", "text": "Describe this image" }
]
}
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.jpg"
}
},
{ "type": "text", "text": "Describe this image" }
]
}
{
"role": "user",
"parts": [
{
"fileData": {
"mimeType": "image/jpeg",
"fileUri": "https://example.com/image.jpg"
}
},
{ "text": "Describe this image" }
]
}
{
"type": "image_url",
"image_url": {
"url": "data:image/jpeg;base64,/9j/4AAQSkZJRg..."
}
}
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRg..."
}
}
{
"inlineData": {
"mimeType": "image/jpeg",
"data": "/9j/4AAQSkZJRg..."
}
}

Conversion Rules:

  • OpenAI’s data: URI is parsed, media_type extracted from URI prefix, pure base64 part passed to target protocol.
  • Claude requires separate media_type field, doesn’t accept data: URI.
  • Gemini uses inlineData instead of fileData for base64 content.
  • OpenAI’s detail parameter (low/high) is lost in conversion; Claude and Gemini have no equivalent concept.
ParameterSource ProtocolCannot Convert ToReason
top_kClaude, GeminiOpenAIOpenAI doesn’t support top-k sampling
nOpenAI, GeminiClaudeClaude doesn’t support multi-candidate generation
frequency_penalty / presence_penaltyOpenAI, GeminiClaudeClaude has no penalty parameters
logprobsOpenAIClaude, GeminiOnly OpenAI models return log probabilities
thinkingClaudeOpenAI, GeminiClaude-specific extended thinking configuration
cache_controlClaudeOpenAI, GeminiClaude-specific prompt caching control
reasoning_effortOpenAIClaude, GeminiOpenAI o1 series-specific parameter
response_format (JSON Schema)OpenAIClaude, GeminiFull JSON Schema constraints only OpenAI supports

RouteAPI uses the following strategies:

  1. Silent Ignore: Unsupported parameters are dropped during conversion without affecting request success (e.g., detail, logprobs).
  2. Auto-Adjust: Out-of-range values are truncated to legal range (e.g., temperature > 1 truncated to 1 when forwarding to Claude).
  3. Conservative Degradation: Complex features simulated with simple methods (e.g., OpenAI’s JSON Schema degrades to tool calls or prompt constraints on Claude).
  4. Reject Request: Rarely, if core parameters cannot be converted and have no reasonable default, return 400 error (e.g., Claude protocol missing max_tokens).

RouteAPI provides conversion information in response headers and logs:

X-RouteAPI-Protocol-Conversion: openai-to-claude
X-RouteAPI-Dropped-Params: frequency_penalty,presence_penalty

If conversion fails or parameters conflict, a standard error response is returned:

{
"error": {
"message": "Parameter 'max_tokens' is required for Claude models",
"type": "invalid_request_error",
"param": "max_tokens",
"code": "missing_required_parameter"
}
}
  1. Use Native Protocol: Use the model’s native protocol whenever possible to avoid conversion loss.
  2. Avoid Protocol-Specific Features: Don’t rely on capabilities unique to a single protocol like logprobs, thinking unless you’re certain to only use that protocol’s models.
  3. Check Response Headers: Pay attention to X-RouteAPI-Dropped-Params header to know which parameters were ignored.
  4. Test Cross-Protocol Compatibility: Validate the same request’s behavior across different protocol/model combinations in test environments.
  5. Log Model ID: Record the actual model ID and protocol type called in logs for troubleshooting differences.
Protocolinput token fieldoutput token fieldtotal token field
OpenAIprompt_tokenscompletion_tokenstotal_tokens
Claudeinput_tokensoutput_tokensNone (calculate yourself)
GeminipromptTokenCountcandidatesTokenCounttotalTokenCount

Conversion Rules:

  • Claude → OpenAI: input_tokens → prompt_tokens, output_tokens → completion_tokens, calculate total_tokens = input_tokens + output_tokens.
  • Gemini → OpenAI: promptTokenCount → prompt_tokens, candidatesTokenCount → completion_tokens, totalTokenCount → total_tokens.
  • OpenAI → Claude: prompt_tokens → input_tokens, completion_tokens → output_tokens, drop total_tokens.

Claude’s prompt caching fields are also preserved:

{
"usage": {
"prompt_tokens": 120,
"completion_tokens": 45,
"total_tokens": 165,
"cache_creation_input_tokens": 80,
"cache_read_input_tokens": 40
}
}
OpenAIClaudeGeminiMeaning
stopend_turnSTOPNatural completion
lengthmax_tokensMAX_TOKENSReached length limit
tool_callstool_useSTOP (with functionCall)Requesting tool call
content_filterNo equivalentSAFETYBlocked by content filter
stopstop_sequenceSTOPHit stop sequence

Conversion Rules:

  • Claude end_turn → OpenAI stop
  • Claude max_tokens → OpenAI length
  • Claude tool_use → OpenAI tool_calls
  • Gemini STOP mapped to stop or tool_calls based on presence of functionCall
  • Gemini SAFETY → OpenAI content_filter

All protocol error responses are converted to OpenAI format (when client uses OpenAI protocol):

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

Claude original error:

{
"type": "error",
"error": {
"type": "authentication_error",
"message": "invalid x-api-key"
}
}

After conversion to OpenAI format, type maps to invalid_request_error, code set to invalid_api_key.

Example 1: OpenAI Request → Claude Format

Section titled “Example 1: OpenAI Request → Claude Format”

Original OpenAI Request:

{
"model": "claude-sonnet-4-5",
"messages": [
{
"role": "system",
"content": "You are a rigorous technical assistant, keep answers concise."
},
{
"role": "user",
"content": "Explain what an API gateway is"
}
],
"temperature": 0.7,
"max_tokens": 150,
"stream": false
}

Converted Claude Request:

{
"model": "claude-sonnet-4-5",
"system": "You are a rigorous technical assistant, keep answers concise.",
"messages": [
{
"role": "user",
"content": "Explain what an API gateway is"
}
],
"temperature": 0.7,
"max_tokens": 150,
"stream": false
}

Key Changes:

  • system message extracted from messages array to top-level system field.
  • messages now only contains user and assistant messages.

Example 2: Claude Tool Call → OpenAI Format

Section titled “Example 2: Claude Tool Call → OpenAI Format”

Claude Tool Call Response:

{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"model": "claude-sonnet-4-5",
"content": [
{
"type": "text",
"text": "Let me check Beijing's weather."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "city": "Beijing" }
}
],
"stop_reason": "tool_use",
"usage": {
"input_tokens": 120,
"output_tokens": 45
}
}

Converted to OpenAI Format:

{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"object": "chat.completion",
"created": 1726567890,
"model": "claude-sonnet-4-5",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Let me check Beijing's weather.",
"tool_calls": [
{
"id": "toolu_01A09q90qw90lq917835lq9",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Beijing\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
],
"usage": {
"prompt_tokens": 120,
"completion_tokens": 45,
"total_tokens": 165
}
}

Key Changes:

  • content array split: text block extracted as content field, tool_use block converted to tool_calls array.
  • tool_use.input object serialized to function.arguments JSON string.
  • stop_reason: "tool_use" → finish_reason: "tool_calls".
  • input_tokens → prompt_tokens, output_tokens → completion_tokens, add total_tokens.
  • Add OpenAI format top-level fields: object, created, choices array.

Example 3: Gemini Multimodal → OpenAI Format

Section titled “Example 3: Gemini Multimodal → OpenAI Format”

Gemini Response:

{
"candidates": [
{
"content": {
"parts": [
{
"text": "This image shows a modern user interface with a navigation bar, content area, and sidebar."
}
],
"role": "model"
},
"finishReason": "STOP",
"index": 0
}
],
"usageMetadata": {
"promptTokenCount": 258,
"candidatesTokenCount": 32,
"totalTokenCount": 290
}
}

Converted to OpenAI Format:

{
"id": "chatcmpl-gemini-abc123",
"object": "chat.completion",
"created": 1726567890,
"model": "gemini-2.0-flash-exp",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "This image shows a modern user interface with a navigation bar, content area, and sidebar."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 258,
"completion_tokens": 32,
"total_tokens": 290
}
}

Key Changes:

  • candidates[0].content.parts[0].text → choices[0].message.content.
  • role: "model" → role: "assistant".
  • finishReason: "STOP" → finish_reason: "stop" (lowercase).
  • usageMetadata field names mapped to OpenAI’s usage structure.

Select protocol based on client and model type:

Client TypeTarget ModelRecommended ProtocolReason
OpenAI SDKOpenAI modelsOpenAINative support, zero conversion
Claude CodeClaude modelsClaude MessagesNative support, zero conversion
LangChain / LiteLLMAny modelOpenAIBest ecosystem compatibility
Anthropic SDKClaude modelsClaude MessagesAccess extended thinking, prompt caching
Custom clientAny modelDepends on needsPrefer target model’s native protocol

If business needs cross-model switching, avoid using these features:

  • OpenAI-specific: logprobs, seed, full JSON Schema constraints, reasoning_effort (o1 series)
  • Claude-specific: thinking, cache_control, mcp_servers
  • Gemini-specific: grounding, codeExecution

Universal feature set (all three support):

  • Basic chat conversation (messages / contents)
  • Streaming output (stream)
  • Temperature control (temperature, note range differences)
  • Tool calling (tools, note format differences)
  • Multimodal input (images, note format differences)
  • Stop sequences (stop / stop_sequences / stopSequences)

Validate these scenarios in test environments:

  1. Same protocol, different models: Ensure OpenAI protocol correctly calls Claude and Gemini models.
  2. Different protocols, same model: Verify result consistency when calling the same Claude model via Claude Messages and OpenAI protocols.
  3. Tool call round-trip: Test tool definition, invocation, and result passing across protocols.
  4. Boundary parameters: Test boundary cases like temperature: 1.5 (OpenAI valid, Claude needs truncation), n: 2 (OpenAI supports, Claude doesn’t).
  5. Error handling: Verify upstream errors are correctly converted to client protocol format.

Record the following information for troubleshooting protocol conversion issues:

{
"request_id": "req_abc123",
"client_protocol": "openai",
"model_id": "claude-sonnet-4-5",
"upstream_protocol": "claude",
"conversion_required": true,
"dropped_params": ["frequency_penalty", "logprobs"],
"adjusted_params": {"temperature": {"original": 1.8, "adjusted": 1.0}},
"latency_ms": 856,
"conversion_overhead_ms": 8
}

Key metrics:

  • Conversion success rate: Percentage of 400 errors caused by protocol conversion failures.
  • Conversion latency: Additional latency introduced by protocol conversion (typically 5-15ms).
  • Parameter drop rate: Which parameters are most frequently dropped, whether it affects business.
  • Cross-protocol error rate: Whether OpenAI → Claude calls have higher error rates than OpenAI → OpenAI.

If migrating from one protocol to another:

  1. Phase 1: Dual-write testing: New protocol call results only used for comparison, doesn’t affect business.
  2. Phase 2: Gradual switchover: Small traffic switches to new protocol, monitor error rates and response quality.
  3. Phase 3: Full switchover: Switch all traffic after confirming no anomalies.
  4. Phase 4: Cleanup old code: Remove adapter code for old protocol.

Each phase requires validating:

  • Functional correctness (tool calling, multimodal, streaming output)
  • Response quality (output differences across protocol/model combinations)
  • Performance metrics (latency, token usage, cost)
  • Error handling (network anomalies, rate limits, upstream failures)

Protocol conversion lets you flexibly choose clients and models, but best practice is still to prioritize using the target model’s native protocol. If cross-protocol calling is necessary, thoroughly validate in test environments and monitor conversion-related errors and performance metrics in production.