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. 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.
bashexport 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')
bashcurl -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.\"}]}"
tsimport 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);
pythonimport osimport anthropicclient = 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 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) |
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, 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): 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:
bashcurl -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."}]}'
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"}
httpserver-timing: total;dur=0.280x-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), a refused field, or a prompt longer than the model's context (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) |
404 not_found_error | A model the node does not serve (model_not_available); there is no gateway route here |
429 rate_limit_error | Over your organization's tokens or requests per minute (rate_limited), with Retry-After |
503 overloaded_error | The model runtime is restarting or busy (infer_backend_unavailable), with Retry-After |
Per-model token and request limits are on Infer limits.
json{ "model": "b3os/qwen3.5-9b", "max_tokens": 256, "messages": [{ "role": "user", "content": "Name one river in Oregon." }]}
Opens the dashboard playground with this request filled in (
POST /v1/infer/v1/messages
). The link carries no key.