How FreeLLMAPI Implements the Anthropic Messages API for Claude Code Compatibility
FreeLLMAPI exposes a fully Anthropic-compatible Messages endpoint at POST /v1/messages that translates Claude-formatted requests into the project's internal OpenAI-shaped format, routes them through a shared provider-agnostic pipeline with retry logic and session affinity, and maps responses back to Anthropic's wire protocol.
The Anthropic Messages API implementation in FreeLLMAPI allows Claude Code and other Anthropic-compatible clients to leverage the same free-model routing infrastructure used for OpenAI-style chat completions. This article explores the technical architecture behind the compatibility layer, referencing the actual source code in the tashfeenahmed/freellmapi repository.
Authentication and Request Validation
The Anthropic route validates incoming requests using a unified authentication system that accepts either the standard Anthropic x-api-key header or a bearer token.
API Key Validation
In server/src/routes/anthropic.ts, the authenticate() function (lines 27-33) checks the request headers against the database-stored API key. If the key is missing or incorrect, the route returns a 401 response with an authentication_error payload, ensuring only authorized clients can access the Messages endpoint.
Zod Schema Validation
Request bodies are validated using Zod schemas defined at lines 48-89. The messagesSchema permissively validates the Anthropic request envelope—including messages, max_tokens, tools, and tool_choice—while using .passthrough() to allow unknown fields. This ensures the route remains tolerant to future Anthropic block types without breaking existing clients.
Request Translation to Internal Format
Before routing, the incoming Anthropic payload must be converted to the internal ChatMessage[] format used by FreeLLMAPI's provider-agnostic engine.
Flattening and Block Extraction
The convertRequest() function handles this translation by:
- Flattening any
systemturn into a system message - Extracting text, image, and tool-use blocks from the Anthropic content array
- Converting tool calls and tool results into internal
ChatToolCallandChatMessagestructures - Mapping the Anthropic
tool_choiceenum to the OpenAIChatToolChoiceequivalent
This transformation ensures that regardless of whether the client speaks Anthropic or OpenAI protocol, the core router processes a standardized representation.
Model Resolution and Session Management
FreeLLMAPI supports both explicit model selection and automatic routing based on availability.
Model Mapping
The resolveAnthropicModel() function (lines 66-68) consults services/anthropic-map.ts to resolve model identifiers like claude-3-sonnet-20240229. If the client requests auto or omits the model parameter, the router selects an available free model from the catalog.
Session Affinity
To prevent "flapping" between providers mid-conversation, the implementation supports sticky sessions. When the client sends X-Claude-Code-Session-Id or X-Session-Id, the system invokes setStickyModel and getStickyModel (lines 70-77) to remember the chosen model for the entire session. This ensures consistent behavior across multi-turn conversations.
The Routing Loop and Fallback Logic
The core routing logic implements a robust retry mechanism with MAX_RETRIES = 20.
The main loop (lines 82-125) performs the following steps:
- Requests a free model from the router via
routeRequest - Invokes the provider's
chatCompletionmethod - If a provider returns an empty completion or rate-limit error, records a cooldown via the ratelimit service
- Retries with an alternative model until successful or the retry limit is reached
This logic is shared across all API routes, meaning Anthropic clients benefit from the same fallback infrastructure as OpenAI clients.
Streaming Translation
When stream: true is requested, the route delegates to streamCompletion() (lines 98-137).
Anthropic-Compatible Server-Sent Events
This helper consumes an OpenAI-style stream from the underlying provider and re-emits it as an Anthropic SSE sequence, including:
message_startcontent_block_startandcontent_block_deltamessage_deltamessage_stop
The streaming path maintains the same retry and fallback logic as the non-streaming implementation, ensuring reliability even during high-load scenarios.
Response Mapping and Error Handling
After a successful provider call, the response must be translated back to Anthropic format.
Content and Stop Reason Mapping
The toAnthropicContent() function (lines 11-19) constructs the response content array from text and tool calls, while mapStopReason() (lines 100-105) translates OpenAI finish reasons (like stop or length) to Anthropic stop_reason values (such as end_turn or max_tokens).
Error Sanitization
Errors are processed through sanitizeProviderErrorMessage and sendError (lines 55-78 and 150-170). The system distinguishes between retryable errors (requiring cooldown and fallback) and fatal errors, logging all requests for analytics while preventing sensitive provider details from leaking to clients.
Auxiliary Endpoints
Beyond the core Messages endpoint, the implementation includes supporting routes:
Token Counting
The POST /v1/messages/count_tokens endpoint (lines 390-399) reuses the same request translator to estimate token usage without invoking a provider, allowing clients to check costs before sending large payloads.
Model Listing
The GET /v1/models endpoint (lines 514-576) returns the unified model catalog when the request includes an anthropic-version header, ensuring Claude-compatible clients receive a compatible model list.
Code Examples
The following examples demonstrate how to call the FreeLLMAPI Anthropic endpoint from a Claude-compatible client.
Non-Streaming Request
const response = await fetch('https://YOUR_FREELLMAPI_HOST/anthropic/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_UNIFIED_API_KEY',
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'claude-3-sonnet-20240229',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello, Claude!' }],
}),
});
const result = await response.json();
console.log(result);
Streaming Request
const res = await fetch('https://YOUR_FREELLMAPI_HOST/anthropic/v1/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'YOUR_UNIFIED_API_KEY',
'anthropic-version': '2023-06-01',
},
body: JSON.stringify({
model: 'auto',
max_tokens: 1024,
stream: true,
messages: [{ role: 'user', content: 'Tell me a short story.' }],
}),
});
const reader = res.body?.getReader();
while (true) {
const { done, value } = await reader!.read();
if (done) break;
console.log(new TextDecoder().decode(value));
}
Summary
- FreeLLMAPI acts as a thin adapter between Anthropic's Messages API and its internal OpenAI-shaped routing infrastructure.
- Authentication uses the
x-api-keyheader validated against the unified database, with Zod schemas ensuring protocol compatibility. - The
convertRequest()function inserver/src/routes/anthropic.tstranslates Anthropic payloads to internalChatMessageformat, handling system prompts, tool calls, and multimodal content. - Session affinity via
X-Claude-Code-Session-Idprevents model switching mid-conversation. - A 20-attempt retry loop with automatic fallback and rate-limit cooldowns ensures high availability across free model providers.
- Streaming responses are translated from OpenAI SSE format to Anthropic's event structure (
message_start,content_block_delta, etc.). - Auxiliary endpoints provide token counting and model listing compatible with Anthropic client expectations.
Frequently Asked Questions
Does FreeLLMAPI require separate API keys for Anthropic and OpenAI endpoints?
No. FreeLLMAPI uses a unified API key stored in the database. The same key works for both OpenAI-compatible routes (/v1/chat/completions) and Anthropic-compatible routes (/v1/messages). The authenticate() function in server/src/routes/anthropic.ts validates the x-api-key header against this single credential store.
How does the router handle rate limits when multiple providers are available?
When a provider returns an empty completion or rate-limit error, the routing loop (lines 82-125) records a cooldown via services/ratelimit.ts and automatically retries with an alternative free model. This process repeats up to 20 times (MAX_RETRIES), ensuring that transient provider failures do not disrupt the conversation.
Can I use Claude Code with FreeLLMAPI without modifying the client?
Yes. FreeLLMAPI implements the Anthropic Messages API specification, including the anthropic-version header support and the SSE streaming format that Claude Code expects. Simply point Claude Code to your FreeLLMAPI host and provide your unified API key in the x-api-key header.
What happens when I request a specific Claude model that is currently rate-limited?
If you specify a model like claude-3-sonnet-20240229 and that provider is rate-limited, the router will attempt to fulfill the request from an available instance of that specific model. If none are available, the request will retry until the cooldown expires or the maximum retry count is reached. If you use auto for the model parameter, the system immediately selects an available free model without waiting for the specific rate-limited provider.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →