> ## Documentation Index
> Fetch the complete documentation index at: https://gomodel-feat-vision-routing.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Anthropic Messages API and Token Counting

> Accept Anthropic-style /v1/messages requests in GoModel and route them to any configured provider, with budgets, failover, and usage tracking applied.

## Overview

GoModel accepts the **Anthropic Messages API** request dialect at `POST /v1/messages`,
in addition to its OpenAI-compatible API. Clients and SDKs that speak the Anthropic
format can point at GoModel unchanged.

The request is translated to GoModel's canonical chat type at ingress and runs through
the same pipeline as `/v1/chat/completions` — so virtual models, workflow policy,
budgets, failover, the response cache, usage/cost tracking, and audit logging all
apply. Because every provider implements chat completion, an Anthropic-format request
can be routed to **any** configured provider (OpenAI, Gemini, Bedrock, and others),
not only Anthropic.

This differs from the [passthrough API](/features/passthrough-api): `/p/anthropic/v1/messages`
forwards bytes verbatim to the Anthropic upstream only, while the managed `/v1/messages`
endpoint routes anywhere and is fully managed.

## Native forwarding to Anthropic

When a `/v1/messages` request resolves to an **Anthropic** provider, GoModel
skips the translation round-trip and forwards the original request body
verbatim (rewriting only the `model` field when an alias resolved to a
different name), then relays the provider-native response or SSE stream
unchanged. This preserves everything the canonical translation cannot —
`cache_control` breakpoints, thinking-block signatures, `anthropic-beta`
headers — which coding agents like Claude Code depend on. Rate limits,
budgets, audit logging, and usage tracking still apply, for both streaming
and non-streaming responses.

Native forwarding is automatic. Requests fall back to the translated pipeline
when a feature that operates on the canonical request is in play: guardrails
request patching, the response cache, or failover routing. Requests resolving
to any non-Anthropic provider always translate.

Because the body is forwarded verbatim, none of the [translation limitations](#limitations)
apply on this path: server-tool history (`server_tool_use`, `web_search_tool_result`, …),
container uploads, and any other block the canonical request cannot represent reach
Anthropic unchanged.

## Supported endpoints

| Endpoint | Behavior |
| - | - |
| `POST /v1/messages` | Creates a message through translated model routing. Supports streaming (`stream: true`) with Anthropic-format SSE events. |
| `POST /v1/messages/count_tokens` | Counts input tokens. Exact for models whose provider has a counting endpoint (Anthropic, OpenAI); a calibrated estimate otherwise. |
| `GET /v1/models` | Returns the catalog in the Anthropic list shape (`type: "model"`, `display_name`, `created_at`) when the request carries the `anthropic-version` header the Anthropic SDKs always send; OpenAI shape otherwise. The whole catalog comes back in one page unless the client pages with `limit`, `after_id`, or `before_id` (Anthropic paging, `has_more` set accordingly); a cursor that names no listed model is a `400`, so restart from the first page. |
| `GET /v1/models/{model}` | Retrieves one model, in the Anthropic model shape when the request carries `anthropic-version`; OpenAI shape otherwise. A bare ID that works for inference (`gpt-6-luna`) retrieves the listed model it resolves to. Failures (for example an unknown model) use the Anthropic error envelope (`{"type": "error", "error": {...}}`) on the same condition. |
| `POST /v1/messages/batches` | Creates a Message Batch. Inline `requests[].params` are translated per item, so a batch can target any provider with native batch support (Anthropic, OpenAI, …), not only Anthropic. |
| `GET /v1/messages/batches` | Lists batches (`limit`, `after_id`). |
| `GET /v1/messages/batches/{id}` | Retrieves a batch (`processing_status`, `request_counts`, `results_url` once ended). |
| `POST /v1/messages/batches/{id}/cancel` | Cancels a running batch. |
| `DELETE /v1/messages/batches/{id}` | Deletes an ended batch. Deleted upstream too when the provider's batch API supports it (Anthropic); otherwise removed from the gateway only. |
| `GET /v1/messages/batches/{id}/results` | Streams results as JSONL. Successful items are returned in the Anthropic Messages shape regardless of the provider that served them. |

## Message Batches

`/v1/messages/batches` shares the gateway's native-batch pipeline with the
OpenAI-compatible `/v1/batches` route — the two are dialect views of the same
resource. Batch IDs are interchangeable across the two dialects (`msgbatch_<uuid>`
here, `batch_<uuid>` there). All requests in one batch must resolve to a single
provider; per-item `custom_id` values are required and must be unique.

Providers whose batch API is file-based (OpenAI-compatible) receive inline
requests as an automatically uploaded JSONL input file. `request_counts` maps the
provider's aggregate counts: while a batch runs, unfinished requests are reported
as `processing`; once it ends, any remainder is attributed by the batch outcome
(`canceled`, `expired`, or `errored`).

```python theme={null}
batch = client.messages.batches.create(requests=[
    {"custom_id": "q1", "params": {"model": "openai/gpt-4o-mini", "max_tokens": 64,
        "messages": [{"role": "user", "content": "What is 2+3?"}]}},
])
# poll client.messages.batches.retrieve(batch.id) until "ended", then:
for entry in client.messages.batches.results(batch.id):
    print(entry.custom_id, entry.result.type)
```

## Authentication

Both credential styles work, so the official Anthropic SDKs are drop-in:

* `Authorization: Bearer <key>` — GoModel's primary scheme.
* `x-api-key: <key>` — the Anthropic-native header, accepted as a fallback when no
  `Authorization` header is present.

```python theme={null}
import anthropic

client = anthropic.Anthropic(
    api_key="<your GoModel key>",       # sent as x-api-key — works as-is
    base_url="https://your-gateway",
)
client.messages.create(
    model="openai/gpt-4o-mini",          # any configured provider's model
    max_tokens=256,
    messages=[{"role": "user", "content": "Hello"}],
)
```

## Example

<CodeGroup>
  ```bash curl theme={null}
  curl https://your-gateway/v1/messages \
    -H "Authorization: Bearer $GOMODEL_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "model": "claude-sonnet-4-6",
      "max_tokens": 256,
      "system": "Be concise.",
      "messages": [{"role": "user", "content": "Hello"}]
    }'
  ```

  ```python Python theme={null}
  import os

  import anthropic

  client = anthropic.Anthropic(
      base_url="https://your-gateway",
      api_key=os.environ["GOMODEL_KEY"],
  )

  message = client.messages.create(
      model="claude-sonnet-4-6",
      max_tokens=256,
      system="Be concise.",
      messages=[{"role": "user", "content": "Hello"}],
  )

  print(message.content[0].text)
  ```

  ```javascript JavaScript theme={null}
  import Anthropic from "@anthropic-ai/sdk";

  const client = new Anthropic({
    baseURL: "https://your-gateway",
    apiKey: process.env.GOMODEL_KEY,
  });

  const message = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 256,
    system: "Be concise.",
    messages: [{ role: "user", content: "Hello" }],
  });

  console.log(message.content[0].text);
  ```
</CodeGroup>

The response uses the Anthropic Messages shape (`type: "message"`, `content` blocks,
`stop_reason`, `usage`). Errors use the Anthropic error envelope
(`{"type": "error", "error": {...}}`). `max_tokens` is required, as in the Anthropic API.
A provider's content filter is reported as `stop_reason: "refusal"`. If the
provider fails partway through a stream, the stream ends with an `error` event
carrying the provider's own message.

`usage` follows Anthropic semantics for every provider: `input_tokens` excludes
cached tokens, which are reported separately as `cache_read_input_tokens` and
`cache_creation_input_tokens`. For OpenAI-compatible providers, whose
`prompt_tokens` includes cached tokens, GoModel moves `cached_tokens` and
`cache_write_tokens` into those fields, so the three counts add up to the whole
prompt.

Streaming responses emit the Anthropic SSE event sequence (`message_start`,
`content_block_start`/`content_block_delta`/`content_block_stop`, `message_delta`,
`message_stop`).

## Cost tracking and audit logs

`/v1/messages` requests are tracked and audited exactly like the OpenAI-compatible
routes. Cost is computed from the actual provider that served the request, and usage
is recorded under the `/v1/messages` endpoint so it can be filtered in the dashboard.

## Limitations

These limitations apply to the **translated** pipeline — requests routed to a
non-Anthropic provider, or to Anthropic with guardrails, response cache, or
failover engaged. Requests [natively forwarded to Anthropic](#native-forwarding-to-anthropic)
are preserved byte-for-byte apart from the `model` value when an alias
resolved to a different name, and none of the below applies.

`/v1/messages` translates through GoModel's canonical chat type. Anthropic-specific
features that have no canonical equivalent are not preserved end to end:

* **`cache_control`** is preserved on the request, system/content blocks,
  custom tools, and tool-use/tool-result history when routed to Anthropic.
* **`{"role": "system"}` messages inside `messages`** keep their position and
  `cache_control` breakpoints when routed to a Claude 4.8+/5-family model
  (Claude Code appends system reminders this way, and its prompt caching
  depends on them staying in place). On older Claude models, which reject the
  role, their text is hoisted into the top-level system prompt instead.
* **`thinking` and `redacted_thinking` blocks** travel both ways verbatim,
  signatures included: responses carry the `signature` Anthropic issued (as a
  `signature_delta` when streaming), and an assistant turn echoed back is
  replayed unchanged, so thinking-enabled conversations and tool-use loops
  continue correctly. Other providers never see them.
* **Reasoning from a provider that does not sign it** (DeepSeek, Fireworks, …)
  is returned as a `thinking` block with `signature: ""` — the member is
  required by the Anthropic schema, and the empty value says the reasoning is
  unsigned. Echo the turn back as-is: the block replays to that provider like
  any other. Anthropic accepts only signatures it minted itself, so a turn
  carrying an unsigned block is stripped of it before reaching Claude (the rest
  of the turn is sent unchanged).
* **`tool_result.is_error`** is preserved when routed to Anthropic. Other
  providers have no equivalent flag and receive the result content only.
* **`tool_use.extra_content`** carries another provider's replay state, such as
  a Gemini 3 thought signature, back to that provider. GoModel sets it on
  `tool_use` blocks it returns; echo it unchanged. See
  [Extra content, thinking blocks, and thought signatures](/advanced/extra-content).
* **Server/built-in tools** (web search, code execution, …) and their history
  blocks (`server_tool_use`, `web_search_tool_result`, …) are rejected with a clear
  `400`; only custom tools (`type` absent or `"custom"`) translate.
* **`top_k`** is dropped — it has no portable OpenAI-compatible equivalent, and
  OpenAI-family providers reject unknown request fields. `temperature` and `top_p`
  are forwarded.
* **Images and documents inside `tool_result` blocks** (screenshots, image and
  PDF files read by a tool — Claude Code returns them this way) are forwarded as
  native `image` and `document` blocks when routed to Anthropic. OpenAI receives
  them through its Responses API, where the model can see them (see
  [OpenAI requests served through Responses](/providers/overview#provider-notes)).
  Other providers receive only the text portion of the tool result.
* **`document` blocks** (PDF, plain text, URL, or Files API `file_id` sources)
  translate to the OpenAI-style `file` content part (`file_data` for inline
  content, `file_url` for remote URLs, `file_id` for uploads). Anthropic gets the
  document back natively with its `title`; Gemini receives inline data only;
  OpenAI-compatible providers receive the `file` part as-is. On OpenAI, an
  untitled PDF gets a filename (`document.pdf`), and URL documents go through
  the Responses API. A plain-text document on a request that stays on Chat
  Completions is sent as text (headed by its `title`), because only PDF file
  data is accepted there; on a request served through the Responses API, such
  as one with a text document inside a `tool_result`, it stays a file.
  Citation settings and `context` are dropped. The custom-content variant
  (`source.type: "content"`) degrades to text.
* **`search_result` blocks** degrade to text (title, source URL, and content);
  citation metadata is dropped.
* **Other content blocks** (`container_upload`, `tool_reference`, …) are rejected
  with a clear `400` error rather than silently dropped.
* **`stop_sequences`** are honored on every provider. Providers that report the
  matched sequence natively (Anthropic) get the full contract back:
  `stop_reason: "stop_sequence"` plus the `stop_sequence` value, and so do
  OpenAI reasoning models (GPT-5 and later, o-series), where GoModel cuts the
  output itself because OpenAI rejects `stop` for them. Other OpenAI-family
  models conflate stop-parameter hits with natural stops in `finish_reason`,
  so completions there report `stop_reason: "end_turn"` (output is still
  truncated correctly).
* **`output_config`**: `effort` sets the reasoning effort (taking precedence
  over the one derived from `thinking`), and `format` (or the older top-level
  `output_format`) becomes JSON-schema structured output — strict when the
  schema meets OpenAI's strict-mode rules (`additionalProperties: false`,
  every property required, and no `allOf`, `not`, or conditional keywords).
  Other schemas are sent with `strict: false`: they guide the output but do
  not guarantee it. A format other than `json_schema` is rejected with a `400`.
* **`thinking`** on a non-Anthropic model becomes a reasoning effort (budget
  under 10000 tokens: `low`, under 20000: `medium`, otherwise `high`; adaptive:
  `medium`), sent as each provider's own field: `reasoning_effort` on OpenAI,
  Groq, Fireworks, and xAI. Models that reject the field (OpenAI `gpt-4*`, Groq
  models other than gpt-oss and qwen3, xAI's `-non-reasoning`, `grok-build`,
  `grok-2` and `grok-3` except `grok-3-mini`) get the request without it
  instead of an error.
  Reasoning comes back as `thinking` blocks whichever member the provider uses
  for it (`reasoning_content` or `reasoning`).
* **`count_tokens`** is exact when the model's provider can count tokens itself:
  Anthropic models are counted by Anthropic's own `count_tokens` endpoint. The
  forwarded request carries the resolved model (an alias is resolved first)
  and only the members that endpoint accepts: `messages`, `system`, `tools`,
  `tool_choice`, `thinking`, `cache_control`, and `output_config`; `max_tokens`,
  `stream`, `metadata`, and the sampling controls are left out because Anthropic
  rejects them there. OpenAI models are counted by OpenAI's
  `/v1/responses/input_tokens` endpoint for the request as GoModel will send
  it. On GPT-5 and later the count matches the billed input (within a token):
  for a request Chat Completions serves with tools, the fixed tools preamble it
  bills is added. On older model families such a request can count a few tokens
  below the billed input (6 on `gpt-4o-mini`). Guardrails do not run for a
  count, so when the request's workflow has prompt guardrails the prompt is
  never sent to a provider for counting and the estimate answers (response- and
  stream-only guardrails keep exact counts). For every other provider, and whenever
  that call fails, the gateway answers with an estimate that weights text by
  how densely it tokenizes (prose, code and JSON, CJK, emoji), adds per-message
  and per-tool framing plus the tool-use system prompt, and prices images by
  their pixel area. On ordinary agent traffic the estimate lands within about
  ten percent of a tokenizer-exact count; raw base64 in text stays
  under-counted, and an image whose size cannot be read is charged the largest
  size the provider keeps. The same estimate seeds `usage.input_tokens` in the
  streaming `message_start` event; the authoritative counts arrive in the final
  `message_delta` event, which SDK accumulators prefer.

For byte-exact Anthropic fidelity beyond the supported cache controls, use the
`/p/anthropic/v1/messages` passthrough route instead.

See [ADR-0007](https://github.com/ENTERPILOT/GoModel/blob/main/docs/adr/0007-anthropic-messages-ingress.md)
for the design rationale and tradeoffs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.