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 system turn into a system message
  • Extracting text, image, and tool-use blocks from the Anthropic content array
  • Converting tool calls and tool results into internal ChatToolCall and ChatMessage structures
  • Mapping the Anthropic tool_choice enum to the OpenAI ChatToolChoice equivalent

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:

  1. Requests a free model from the router via routeRequest
  2. Invokes the provider's chatCompletion method
  3. If a provider returns an empty completion or rate-limit error, records a cooldown via the ratelimit service
  4. 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_start
  • content_block_start and content_block_delta
  • message_delta
  • message_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-key header validated against the unified database, with Zod schemas ensuring protocol compatibility.
  • The convertRequest() function in server/src/routes/anthropic.ts translates Anthropic payloads to internal ChatMessage format, handling system prompts, tool calls, and multimodal content.
  • Session affinity via X-Claude-Code-Session-Id prevents 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →