# How FreeLLMAPI Implements the Anthropic Messages API for Claude Code Compatibility

> FreeLLMAPI provides Anthropic Messages API compatibility for Claude. Learn how it translates requests, routes them through a unified pipeline, and maps responses for seamless integration.

- Repository: [Tashfeen/freellmapi](https://github.com/tashfeenahmed/freellmapi)
- Tags: how-to-guide
- Published: 2026-06-25

---

**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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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

```typescript
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

```typescript
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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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.