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, notANTHROPIC_API_KEY. The auth-token variable sendsAuthorization: Bearer …, which is the only header MindsHub accepts.ANTHROPIC_API_KEYsendsx-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/messagespath itself. -
Optional: give MindsHub its own config directory.
ANTHROPIC_AUTH_TOKENoutranks 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 startup401 Authorization Requiredshows 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
opusalias, 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).
- Python
- 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)
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://api.mindshub.ai",
authToken: process.env.MINDSHUB_API_KEY, // authToken, not apiKey
});
const message = await client.messages.create({
model: "sonnet",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
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
claudethat contains a family name (opus,sonnet,haiku, orfable; a substring match, checked in that order) maps to the corresponding alias.claude-sonnet-5,claude-sonnet-4-6-20250929, andclaude-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;
systemas a string or content blocks. - Images (
imageblocks withbase64orurlsources). - Client tool use:
toolswithinput_schema,tool_use/tool_resultblocks round-trip,is_errorpreserved. A tool'sstrictflag is carried to targets that have one. tool_choice:auto,any,none, and{"type": "tool", "name": ...}, plusdisable_parallel_tool_use.- Structured output:
output_config.formatwith ajson_schemaconstrains 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 returns400 param_not_supportedrather than prose. See Structured output. - Reasoning depth:
output_config.effortis 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*andweb_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_controlbreakpoints are honored on Claude-family targets. Cache usage is reported in the response'susage. - Extended thinking: a
thinkingobject on the request is forwarded to the target model where that model accepts it, including itsbudget_tokens. The current Claude 5 models accept it.thinkingandredacted_thinkingblocks 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).
| Feature | Behavior |
|---|---|
metadata | Ignored. |
service_tier, container, inference_geo | Ignored. |
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 API | Not implemented. POST /v1/messages/batches returns a plain 404. |
| Citations / search-result blocks | Not 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
}
}
contentblocks aretext,tool_use, and, on models that think by default (opus,fable), a leadingthinkingblock, usually with empty or withheld content plus asignature. 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_reasonis one ofend_turn,tool_use,max_tokens,stop_sequence,refusal. On a Claude-family target the model's own word is reported, so a stop yourstop_sequencestriggered comes back asstop_reason: "stop_sequence"with the matched string instop_sequence, and a refusal carriesstop_detailson streamed and non-streamed turns alike. On other targets the upstream only tells us as much as OpenAI'sfinish_reasondoes, sostop_sequenceis not distinguishable fromend_turnthere.- Beta-only stop reasons are mapped onto stable ones.
model_context_window_exceededandcompactionexist in Anthropic's beta Messages surface, not the stable one this endpoint serves, so publishing them would hand your SDK a value its ownStopReasontype doesn't contain. A context-window overrun arrives asmax_tokens, which is the same instruction Anthropic's own docs give for it — treat the response as truncated. pause_turnis never reported, and a paused turn arrives asend_turn. A pause means a server-tool loop hit its iteration cap and asks you to send the assistant content back to continue — but theserver_tool_useand*_tool_resultblocks 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
usagefields are always present. As in Anthropic's own API,input_tokensexcludes 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:
pingis sent once, right aftermessage_start, not periodically. Don't use pings as a liveness signal.- On non-Claude models,
message_startreportsinput_tokens: 0. The real counts arrive in the finalmessage_delta. Token-tracking UIs will undercount mid-stream on non-Claude models. - A failure after the body opens arrives as an
errorevent, 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 ordinarymessage_deltawithstop_reason: "end_turn"followed bymessage_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 typedapi_error. Read the HTTP status, not just the type string. - Denial headers survive the envelope. A 429 carries
Retry-Afterand theX-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 prefixedInvalid request body:), not the422the 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.