> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beta.b3os.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Anthropic compatibility

Use the Anthropic SDKs and the Messages API with the model served on the B3OS node; which request fields are supported, accepted and ignored, or refused.

`/v1/infer` also speaks the Anthropic Messages API, on the same model as the [OpenAI routes](/compat/openai). The Anthropic SDKs work with two changes: the base URL, and your B3OS key as the credential.

| Where your code runs | Base URL | Key |
|---|---|---|
| Anywhere | `https://beta-api.b3os.org/v1/infer` | Your B3OS key |
| Inside a B3OS sandbox | `http://127.0.0.1:7700/v1/infer` | Not checked; pass any string |

The SDKs add `/v1/messages`, so requests go to `/v1/infer/v1/messages`. The key works as a bearer token (`ANTHROPIC_AUTH_TOKEN`, `authToken`, `auth_token`) and as `x-api-key` (`ANTHROPIC_API_KEY`), so SDK releases that send either header authenticate.

```bash
export ANTHROPIC_BASE_URL="https://beta-api.b3os.org/v1/infer"
export ANTHROPIC_AUTH_TOKEN="$B3OS_API_KEY"
export B3OS_MODEL=$(curl -s https://beta-api.b3os.org/v1/infer/models \
  -H "Authorization: Bearer $B3OS_API_KEY" | jq -r '[.data[] | select(.b3os.kind == "chat")][0].id')
```

**curl**

```bash
    curl -s https://beta-api.b3os.org/v1/infer/v1/messages \
      -H "Authorization: Bearer $B3OS_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "Content-Type: application/json" \
      -d "{\"model\": \"$B3OS_MODEL\", \"max_tokens\": 256, \"messages\": [{\"role\": \"user\", \"content\": \"Name one river in Oregon.\"}]}"
    ```
  **TypeScript**

```ts
    import Anthropic from "@anthropic-ai/sdk";

    const client = new Anthropic({
      authToken: process.env.B3OS_API_KEY,
      baseURL: "https://beta-api.b3os.org/v1/infer",
    });
    const message = await client.messages.create({
      model: process.env.B3OS_MODEL!,
      max_tokens: 256,
      messages: [{ role: "user", content: "Name one river in Oregon." }],
    });
    console.log(message.content, message.usage);
    ```
  **Python**

```python
    import os
    import anthropic

    client = anthropic.Anthropic(
        auth_token=os.environ["B3OS_API_KEY"],
        base_url="https://beta-api.b3os.org/v1/infer",
    )
    message = client.messages.create(
        model=os.environ["B3OS_MODEL"],
        max_tokens=256,
        messages=[{"role": "user", "content": "Name one river in Oregon."}],
    )
    print(message.content, message.usage)
    ```

[Messages](/infer/messages) shows streaming, tools and thinking with both SDKs.

## Request fields

**Supported**

| Field | On this node |
|---|---|
| `model`, `max_tokens` | Both required. `max_tokens` caps the answer, and admission reserves it against your token limit |
| `messages` | `text`, `image` (`base64`, or a `url` source the node fetches through its egress guard), `tool_use`, `tool_result` and `thinking` blocks. A trailing assistant turn that ends in text is continued |
| `system` | A string or text blocks, joined with blank lines |
| `tools`, `tool_choice` | Custom tools (`name`, `description`, `input_schema`, `strict`), up to 128; `tool_choice` `auto`, `any`, `tool` or `none`, with `disable_parallel_tool_use` |
| `thinking` | `enabled` with `budget_tokens` (from 1,024, below `max_tokens`), `adaptive` or `disabled`. The reasoning comes back in `thinking` blocks |
| `output_config.format` | JSON Schema output (`json_schema`) |
| `stop_sequences` | Up to 4 |
| `temperature`, `top_p`, `top_k` | Passed to the model's runtime (`temperature` and `top_p` from 0 to 1). Older SDK releases and agents still send them, so they are kept |
| `stream` | The Messages event stream, `message_start` to `message_stop` |

`POST /v1/infer/v1/messages/count_tokens` counts a request's input tokens with the model's own chat template, for free.

**Accepted and ignored**

| Field | Why |
|---|---|
| `cache_control`, on any block or tool | The runtime caches prompt prefixes on its own, per organization |
| `context_management` | Nothing is cleared from the conversation: the model sees every turn you send (see [long sessions](/infer/messages#long-sessions)) |
| `citations` on text blocks, `metadata.user_id`, `service_tier` | No effect on this node |
| `output_config.effort` | No effect; `thinking` controls the reasoning |
| `thinking.display` | The reasoning is always returned |

**Refused** with `400` and the B3OS code [`unsupported_parameter`](/errors#unsupported_parameter), naming the field

| Field | Why |
|---|---|
| Tools Anthropic defines or runs (`web_search`, `bash`, `code_execution`, `computer`, `text_editor` and the like) | The node runs custom tools only |
| `mcp_servers`, `container` | Not available on this node |
| `document` blocks and `file` sources (the Files API) | Send text, or the image as base64 or a URL |
| Any other field | The request is closed, as Anthropic refuses extra inputs |

A `system` message inside `messages` is refused too (`400`, [`invalid_request`](/errors#invalid_request)): instructions go in the top-level `system`.

## Errors

Errors use Anthropic's shape, `{"type": "error", "error": {"type", "message"}}`, with the B3OS code and the offending field under `b3os`. That holds for errors raised before the request reaches the model service too (authentication, scopes, body size), on the node and at the edge, so the SDKs raise the same exception classes they raise for Anthropic's API. Nothing maps another provider's model name to the node's model, so code that names a Claude model gets a `404`:

**Request**

```bash
curl -s -X POST 'https://beta-api.b3os.org/v1/infer/v1/messages' \
  -H "Authorization: Bearer $B3OS_API_KEY" \
  -H 'content-type: application/json' \
  -H 'anthropic-version: 2023-06-01' \
  -d '{"model":"claude-sonnet-4-5","max_tokens":256,"messages":[{"role":"user","content":"Name one river in Oregon."}]}'
```
**Response 404**

```json
{
  "b3os": {
    "code": "model_not_available",
    "param": "model"
  },
  "error": {
    "message": "claude-sonnet-4-5 is not served on this node; GET /v1/infer/models lists the models that are",
    "type": "not_found_error"
  },
  "request_id": "req_01m69wb38zspekwybkv2gfz3ey",
  "type": "error"
}
```
**Server-Timing**

```http
server-timing: total;dur=0.280
x-request-id: req_01m69wb38zspekwybkv2gfz3ey
```

Captured 2026-10-31 from commit `6bd0d80a`, label `dev` (recorded on a dev node before this reached production). Case `infer/messages-unknown-model`.

| Status (`error.type`) | When |
|---|---|
| `400 invalid_request_error` | A malformed value ([`invalid_request`](/errors#invalid_request)), a refused field, or a prompt longer than the model's context ([`context_length_exceeded`](/errors#context_length_exceeded)) |
| `401 authentication_error` | No key, or a key that is not valid |
| `403 permission_error` | A key without the `infer:run` scope, or an image URL on a private address ([`egress_denied`](/errors#egress_denied)) |
| `404 not_found_error` | A model the node does not serve ([`model_not_available`](/errors#model_not_available)); there is no gateway route here |
| `429 rate_limit_error` | Over your organization's tokens or requests per minute ([`rate_limited`](/errors#rate_limited)), with `Retry-After` |
| `503 overloaded_error` | The model runtime is restarting or busy ([`infer_backend_unavailable`](/errors#infer_backend_unavailable)), with `Retry-After` |

Per-model token and request limits are on [Infer limits](/infer/limits).

```json
{
  "model": "b3os/qwen3.5-9b",
  "max_tokens": 256,
  "messages": [{ "role": "user", "content": "Name one river in Oregon." }]
}
```

[Try it in the playground](https://beta.b3os.org/app/playground?op=inferCreateMessage&body=eyJtb2RlbCI6ImIzb3MvcXdlbjMuNS05YiIsIm1heF90b2tlbnMiOjI1NiwibWVzc2FnZXMiOlt7InJvbGUiOiJ1c2VyIiwiY29udGVudCI6Ik5hbWUgb25lIHJpdmVyIGluIE9yZWdvbi4ifV19)

Opens the dashboard playground with this request filled in (`POST /v1/infer/v1/messages`). The link carries no key.