# How FreeLLMAPI Implements the Anthropic Messages API for Claude Clients

> Learn how FreeLLMAPI implements the Anthropic Messages API for Claude clients. It handles authentication, validation, translation, and retries for seamless integration.

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

---

**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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/services/ratelimit.ts) and [`lib/error-classify.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/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:

```typescript
// 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();

```

```typescript
// 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-key` header against a unified database key, with Zod schemas validating Anthropic-specific payloads in [`server/src/routes/anthropic.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/anthropic.ts).
- **Translation Layer** converts Anthropic Messages API requests to an internal OpenAI-shaped `ChatMessage[]` format via `convertRequest()`, handling system prompts, tool calls, and content blocks.
- **Model Resolution** maps Claude model identifiers (like `claude-3-sonnet-20240229`) to internal catalog entries using [`services/anthropic-map.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/services/anthropic-map.ts), supporting `auto` routing for free models.
- **Session Affinity** maintains model consistency across conversations when `X-Claude-Code-Session-Id` is present, preventing provider switching mid-session.
- **Retry Logic** implements up to 20 retries with cooldown management through [`services/ratelimit.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/services/ratelimit.ts) when providers return empty completions or rate limits.
- **Streaming Support** re-emits OpenAI provider streams as Anthropic SSE events (`message_start`, `content_block_delta`, etc.) via `streamCompletion()`.
- **Response Mapping** converts internal formats back to Anthropic-compatible JSON using `toAnthropicContent()` and `mapStopReason()`.

## 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`](https://github.com/tashfeenahmed/freellmapi/blob/main/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`](https://github.com/tashfeenahmed/freellmapi/blob/main/services/ratelimit.ts), classifies the error using [`lib/error-classify.ts`](https://github.com/tashfeenahmed/freellmapi/blob/main/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.