Skip to main content

Anthropic compatibility

POST /v1/messages implements the Anthropic Messages API shape, so Anthropic SDKs and the tools built on them, most notably Claude Code, run against MindsHub with a base-URL and auth-token change. Model names resolve through the same catalog as everywhere else, so you can point an Anthropic client at any MindsHub model: gpt, kimi, gemini-flash, not just Claude.

Claude Code

export ANTHROPIC_BASE_URL="https://api.mindshub.ai"
export ANTHROPIC_AUTH_TOKEN="$MINDSHUB_API_KEY"
claude

Three rules:

  • Use ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY. The auth-token variable sends Authorization: Bearer …, which is the only header MindsHub accepts. ANTHROPIC_API_KEY sends x-api-key, which is rejected with a 401 before the request reaches the API.

  • The base URL is the host only, no /v1. The client adds the /v1/messages path itself.

  • Optional: give MindsHub its own config directory. ANTHROPIC_AUTH_TOKEN outranks a stored claude.ai login, so this isn't required for authentication. It keeps MindsHub sessions and settings separate from your regular profile, and it is the fix if a startup 401 Authorization Required shows Claude Code preferring its stored login anyway (reported on some 2.1.x setups):

    export CLAUDE_CONFIG_DIR="$HOME/.claude-mindshub"

Three cost notes for Claude Code specifically:

  • Its default model maps to the opus alias, one of the most expensive in the catalog. Pick a cheaper model in Claude Code (/model) if cost matters.
  • Claude Code makes heavy use of prompt caching; cache writes are billable and never draw included tokens (see Billing).
  • The cost figure Claude Code displays is its own estimate and doesn't reflect MindsHub billing, especially on non-Claude aliases. Your usage summary is authoritative.

Anthropic SDKs

Install with pip install anthropic (Python) or npm install @anthropic-ai/sdk (TypeScript).

import os
import anthropic

client = anthropic.Anthropic(
base_url="https://api.mindshub.ai",
auth_token=os.environ["MINDSHUB_API_KEY"], # auth_token, not api_key
)

message = client.messages.create(
model="sonnet",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)

The anthropic-version header your SDK sends is accepted and ignored.

Model names

The model field accepts two forms:

  • A MindsHub alias: sonnet, opus, gpt, kimi, any alias from Models. This is how you run Claude Code on a non-Claude model.
  • A real Claude model name: anything starting with claude that contains a family name (opus, sonnet, haiku, or fable; a substring match, checked in that order) maps to the corresponding alias. claude-sonnet-5, claude-sonnet-4-6-20250929, and claude-opus-5[1m] all work; version and date segments are ignored, and the family decides the alias. This is what lets Claude Code's own model picker work unmodified.

A claude… name with no recognizable family word returns 404 model_not_found; there is no silent guessing.

The response's model field echoes back exactly the string you sent (unlike /v1/chat/completions, which reports the resolved ID).

What's supported

  • Text and multi-turn conversations; system as a string or content blocks.
  • Images (image blocks with base64 or url sources).
  • Client tool use: tools with input_schema, tool_use / tool_result blocks round-trip, is_error preserved. A tool's strict flag is carried to targets that have one.
  • tool_choice: auto, any, none, and {"type": "tool", "name": ...}, plus disable_parallel_tool_use.
  • Structured output: output_config.format with a json_schema constrains the answer on any target model. Your schema is forwarded as written — nothing is silently rewritten to make it fit — and a model that cannot constrain its output returns 400 param_not_supported rather than prose. See Structured output.
  • Reasoning depth: output_config.effort is clamped onto the target model's published ladder rather than dropped, so the closest level the model actually offers is used.
  • Anthropic's hosted web tools: web_search* and web_fetch* tool types map onto the platform's web search where the target model supports it (see Chat completions → Built-in web search).
  • Prompt caching: cache_control breakpoints are honored on Claude-family targets. Cache usage is reported in the response's usage.
  • Extended thinking: a thinking object on the request is forwarded to the target model where that model accepts it, including its budget_tokens. The current Claude 5 models accept it. thinking and redacted_thinking blocks in your message history are replayed to the model unmodified, so a multi-turn tool workflow on a thinking model keeps working. Reasoning bills as output tokens. See the response-shape notes below for the streaming limitation.
  • Streaming (see below), and POST /v1/messages/count_tokens.

What's not supported

These request features are accepted but ignored: the request succeeds and the feature silently doesn't happen (the same accepted-and-ignored rule as the OpenAI-compatible endpoints).

FeatureBehavior
metadataIgnored.
service_tier, container, inference_geoIgnored.
Top-level cache_control (auto-caching)Ignored. Block-level cache_control breakpoints are honored — see above.
Server tools (computer_*, bash_*, text_editor_*, code_execution, memory)Dropped from the request.
mcp_servers (server-side MCP)Ignored. (Claude Code's own MCP support is client-side and unaffected.)
Files API references ("source": {"type": "file"})Unsupported, and behavior is target-dependent: some providers reject them, others ignore them. Use base64 or url image sources.
Message Batches APINot implemented. POST /v1/messages/batches returns a plain 404.
Citations / search-result blocksNot returned. Web-search results reach you only as text the model wrote.

Sampling parameters (temperature, top_p, top_k, stop_sequences) follow the same per-model policy as the OpenAI endpoints: forwarded where the target model accepts them, dropped and named in X-MindsHub-Dropped-Params where it doesn't. On the current Claude 5 models (opus, sonnet, fable) that means temperature, top_p, and top_k are dropped while stop_sequences work; haiku forwards everything.

Unknown top-level request fields follow the same rule today: accepted and ignored, not a validation error. Unknown nested content, say an unrecognized block type inside messages, can be forwarded to the provider and fail there instead.

Response shape

{
"id": "msg_5084f82319564e568c6e82fd8520c125",
"type": "message",
"role": "assistant",
"model": "sonnet",
"content": [{"type": "text", "text": "Hello!"}],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 11,
"output_tokens": 4,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
}
}
  • content blocks are text, tool_use, and, on models that think by default (opus, fable), a leading thinking block, usually with empty or withheld content plus a signature. Don't assume the first block is your text.
  • Thinking blocks come back on streamed and non-streamed turns alike, in Anthropic's required order (before the text), with their signature — so a streaming client can replay them on its next request.
  • stop_reason is one of end_turn, tool_use, max_tokens, stop_sequence, refusal. On a Claude-family target the model's own word is reported, so a stop your stop_sequences triggered comes back as stop_reason: "stop_sequence" with the matched string in stop_sequence, and a refusal carries stop_details on streamed and non-streamed turns alike. On other targets the upstream only tells us as much as OpenAI's finish_reason does, so stop_sequence is not distinguishable from end_turn there.
  • Beta-only stop reasons are mapped onto stable ones. model_context_window_exceeded and compaction exist in Anthropic's beta Messages surface, not the stable one this endpoint serves, so publishing them would hand your SDK a value its own StopReason type doesn't contain. A context-window overrun arrives as max_tokens, which is the same instruction Anthropic's own docs give for it — treat the response as truncated.
  • pause_turn is never reported, and a paused turn arrives as end_turn. A pause means a server-tool loop hit its iteration cap and asks you to send the assistant content back to continue — but the server_tool_use and *_tool_result blocks that turn produced are not in the response body, so resuming from it would be incoherent. The answer is truncated rather than resumable.
  • All four usage fields are always present. As in Anthropic's own API, input_tokens excludes cached tokens: the three input fields partition the prompt.

Streaming

"stream": true produces the standard Anthropic event sequence:

event: message_start → event: ping → event: content_block_start
→ event: content_block_delta … → event: content_block_stop
→ event: message_delta → event: message_stop

Differences from Anthropic's native stream:

  • ping is sent once, right after message_start, not periodically. Don't use pings as a liveness signal.
  • On non-Claude models, message_start reports input_tokens: 0. The real counts arrive in the final message_delta. Token-tracking UIs will undercount mid-stream on non-Claude models.
  • A failure after the body opens arrives as an error event, typed exactly as the same failure would have been before the body opened, and the SDKs raise it. It is not a complete failure signal, though: a mid-generation failure can still surface as a normal-looking completion — an ordinary message_delta with stop_reason: "end_turn" followed by message_stop, with truncated or empty content. If your application must detect failed generations, sanity-check the output as well.
  • If the request fails before generation starts, you get a JSON error response (see below), not an SSE stream.

Counting tokens

curl https://api.mindshub.ai/v1/messages/count_tokens \
-H "Authorization: Bearer $MINDSHUB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "sonnet", "messages": [{"role": "user", "content": "Hello"}]}'
{"input_tokens": 8}

Counts come from Anthropic's real counter. Name a concrete Claude model (claude-sonnet-5) and you get that model's own exact count. Send a catalog alias or a non-Claude model and the count runs against the platform's current Claude counting model instead, so read those as close rather than exact: tokenizers differ by roughly 1.3x across Claude generations, and further still off the family. (Anthropic documents its own token counts as estimates too.) If Anthropic is unreachable, or no key is configured, the endpoint still answers from a rough local estimate rather than failing. Budget with these; don't reconcile billing with them. count_tokens calls are free and unmetered.

Errors

Errors on this endpoint use the Anthropic envelope:

{"type": "error", "error": {"type": "not_found_error", "message": "The model 'foo' does not exist or you do not have access to it."}}

The status codes and their meanings are the same as everywhere else; see Errors. Three quirks specific to this endpoint:

  • A 402 (empty wallet) arrives typed invalid_request_error, and a 503 typed api_error. Read the HTTP status, not just the type string.
  • Denial headers survive the envelope. A 429 carries Retry-After and the X-MindsHub-* headers, so back off on the value we send rather than guessing. The param-adaptation headers travel too, on successful requests and on errors the target model itself returned.
  • A malformed request body returns 400 (message prefixed Invalid request body:), not the 422 the OpenAI-compatible endpoints use.

Model discovery

GET /v1/models answers in a shape the Anthropic SDK's client.models.list() parses, so model discovery works alongside everything else. Each row carries type, display_name, and created_at (an epoch value — the catalog tracks availability rather than release date) beside the OpenAI-shaped fields. The listing is never paginated.

Behavior on this page verified against the shipped code in August 2026.

The per-parameter contract this page summarizes is maintained in code, as PARAM_SUPPORT in minds/requests/anthropic_compat.py, and held to the Anthropic SDK's own parameter list by a test.