How FreeLLMAPI Implements the Anthropic Messages API for Claude Clients
FreeLLMAPI exposes a fully compatible Anthropic Messages API endpoint at POST /v1/messages that authenticates requests via the x-api-key header, validates payloads using Zod schemas, translates Claude-formatted messages into an internal OpenAI-shaped format, and routes them through a provider-agnostic retry loop before converting responses back to Anthropic's native wire format.
FreeLLMAPI is an open-source routing layer that unifies access to free LLM providers under a single API. The Anthropic Messages API implementation for Claude clients resides in server/src/routes/anthropic.ts, enabling tools like Claude Code to leverage the same free-model routing infrastructure and analytics that power the platform's OpenAI-compatible endpoints.
Authentication and Request Validation
The Anthropic route begins by validating credentials and sanitizing incoming payloads before translation.
API Key Validation
The authenticate() middleware (lines 27-33) inspects the x-api-key header (or bearer token) and validates it against the unified API key stored in the database. If the key is missing or incorrect, the route immediately returns a 401 authentication_error response, preventing unauthorized access to the routing layer.
Zod Schema Validation
Incoming requests are validated against the messagesSchema defined at lines 48-89 using Zod. The schema permissively validates Anthropic-specific fields including messages, max_tokens, tools, and tool_choice. Unknown fields pass through via .passthrough(), ensuring the route remains tolerant to future Anthropic block types without requiring immediate code changes.
Converting Anthropic Requests to Internal Format
Once authenticated, the request undergoes structural transformation to match the internal ChatMessage[] format used by the provider router.
The convertRequest() Function
The convertRequest() function (lines 94-84) handles the bidirectional translation between Anthropic and OpenAI formats. This includes flattening any system turn into a system message, extracting text, image, and tool-use blocks from the content array, and converting tool calls and tool results into internal ChatToolCall and ChatMessage structures. The function also maps the Anthropic tool_choice enum to the internal ChatToolChoice representation.
Model Resolution via anthropic-map.ts
The requested model (e.g., claude-3-sonnet-20240229) is resolved through services/anthropic-map.ts via resolveAnthropicModel() (lines 66-68). If the client specifies auto or omits the model parameter entirely, the router selects an available free model from the catalog; otherwise, it enforces the pinned model mapping.
Session Affinity and Routing Logic
FreeLLMAPI maintains conversation continuity and handles provider failures through session stickiness and aggressive retry logic.
Sticky Sessions for Claude Code
When the client sends the X-Claude-Code-Session-Id or generic X-Session-Id header, the router invokes setStickyModel and getStickyModel (lines 70-77) to remember the chosen model for the entire session. This prevents "flapping" between different providers mid-conversation, ensuring consistent behavior for Claude Code sessions.
The Retry Loop with MAX_RETRIES
The main routing loop (lines 82-125) implements MAX_RETRIES = 20 to handle provider instability. The loop calls routeRequest from services/router.ts to obtain a free model, executes the provider's chatCompletion (or streaming variant), and processes fallbacks. If a provider returns an empty completion or rate-limit error, the loop records a cooldown via services/ratelimit.ts and retries with another available model.
Streaming and Response Translation
The implementation supports both synchronous and streaming response modes, converting provider output to Anthropic-compatible SSE sequences.
SSE Stream Conversion
When stream: true is set, streamCompletion() (lines 98-137) consumes the OpenAI-style stream from the underlying provider and re-emits it as Anthropic SSE events including message_start, content_block_start, content_block_delta, message_delta, and message_stop. This helper performs the same retry and fallback logic as the non-streaming path, ensuring reliability during streaming sessions.
Mapping Stop Reasons and Content
After a successful provider call, toAnthropicContent() (lines 11-19) constructs the Anthropic content array from internal text and tool call representations. The mapStopReason() function (lines 100-105) translates OpenAI finish reasons (like stop or length) to Anthropic stop_reason values (such as end_turn or max_tokens), ensuring clients receive expected status codes.
Error Handling and Logging
Errors undergo sanitization and classification before reaching the client. The sanitizeProviderErrorMessage utility (from server/src/lib/error-redaction.ts) strips sensitive details, while logRequest records analytics. The sendError function formats responses according to Anthropic's error specification. Rate-limit hits, retryable errors, and model-not-found cases trigger specific cooldowns and fallback counters managed by services/ratelimit.ts and lib/error-classify.ts (lines 55-78 and 150-170).
Utility Endpoints
Beyond the main Messages endpoint, the route provides auxiliary functionality for client tooling.
Token Counting
The POST /v1/messages/count_tokens endpoint (lines 390-399) reuses the request translator to estimate token consumption without invoking an actual provider, allowing clients to preview costs.
Model Listing
The GET /v1/models endpoint (lines 514-576) returns the same catalog used by the OpenAI routes, but filters visibility based on the presence of the anthropic-version header, ensuring Claude-compatible callers only see relevant models.
Client Integration Example
Claude-compatible clients can connect to FreeLLMAPI using standard Anthropic SDK patterns with modified base URLs:
// Non-streaming request example
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();
// Streaming request example
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));
}
Both examples target the /anthropic/v1/messages path, which internally executes the authentication, translation, routing, and response conversion pipeline described above.
Summary
- Authentication occurs via the
x-api-keyheader against a unified database key, with Zod schemas validating Anthropic-specific payloads inserver/src/routes/anthropic.ts. - Translation Layer converts Anthropic Messages API requests to an internal OpenAI-shaped
ChatMessage[]format viaconvertRequest(), handling system prompts, tool calls, and content blocks. - Model Resolution maps Claude model identifiers (like
claude-3-sonnet-20240229) to internal catalog entries usingservices/anthropic-map.ts, supportingautorouting for free models. - Session Affinity maintains model consistency across conversations when
X-Claude-Code-Session-Idis present, preventing provider switching mid-session. - Retry Logic implements up to 20 retries with cooldown management through
services/ratelimit.tswhen providers return empty completions or rate limits. - Streaming Support re-emits OpenAI provider streams as Anthropic SSE events (
message_start,content_block_delta, etc.) viastreamCompletion(). - Response Mapping converts internal formats back to Anthropic-compatible JSON using
toAnthropicContent()andmapStopReason().
Frequently Asked Questions
How does FreeLLMAPI handle authentication for Claude clients?
FreeLLMAPI accepts the standard Anthropic x-api-key header (or bearer token) and validates it against the unified API key stored in the database via the authenticate() function in server/src/routes/anthropic.ts (lines 27-33). This allows Claude Code and other Anthropic clients to use the same API key infrastructure as OpenAI-compatible clients, returning a 401 authentication_error if credentials are invalid.
What happens when a requested model is unavailable or rate-limited?
The routing loop implements MAX_RETRIES = 20 (lines 82-125). If a provider returns an empty completion or rate-limit error, the system records a cooldown via services/ratelimit.ts, classifies the error using lib/error-classify.ts, and automatically falls back to another available free model. For streaming requests, this retry logic operates within streamCompletion() to maintain connection stability.
Does FreeLLMAPI support streaming responses for the Anthropic Messages API?
Yes. When stream: true is specified, the streamCompletion() function (lines 98-137) consumes the OpenAI-style stream from the underlying provider and re-emits it as Anthropic-compatible Server-Sent Events (SSE). The stream includes proper event types such as message_start, content_block_start, content_block_delta, and message_stop, allowing Claude clients to process incremental updates natively.
How does the request translation process work internally?
Incoming Anthropic requests are converted to an internal OpenAI-shaped format by convertRequest() (around line 94). This process flattens system messages, extracts text and image blocks, converts Anthropic tool-use blocks to ChatToolCall structures, and maps tool_choice enums to internal representations. After processing by the router, toAnthropicContent() (lines 11-19) and mapStopReason() (lines 100-105) translate the provider response back to Anthropic's expected wire format.
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 →