Messages
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.
Endpoint
POST https://api.mindshub.ai/v1/messages, plus POST /v1/messages/count_tokens.
Anthropic SDKs take the base URL without /v1 (the client appends /v1/messages itself) and must authenticate with auth_token, not api_key. The api_key field sends an x-api-key header, which MindsHub rejects with a 401 before the request lands. Install the official client: pip install anthropic or npm install @anthropic-ai/sdk. Running Claude Code against MindsHub is covered in Coding agents.
Quick example
- 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="mindshub_air",
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: "mindshub_air",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
console.log(message.content[0]);
The anthropic-version header your SDK sends is accepted and ignored.
Request parameters
| Parameter | Handling | Notes |
|---|---|---|
model | adapted | A real Claude model id is family-mapped to the catalog alias serving that family (claude-sonnet-5 -> sonnet); a bare alias resolves as-is. The response echoes the id the caller sent, not the one that ran. |
messages | forwarded | Text, image, tool_use, tool_result and thinking blocks all round-trip. is_error and block-level cache_control survive the translation to internal shape and back. |
system | forwarded | String or content blocks. Blocks pass through verbatim so cache_control markers reach a Claude-family target. |
stream | forwarded | Produces the standard Anthropic event sequence. See the module docstring for the two documented divergences (ping is sent once, and input tokens are 0 on message_start for non-Claude targets). |
max_tokens | conditional | Clamped to the resolved model's real output ceiling, and up to its floor where the backend enforces one; either adjustment is named in X-MindsHub-Clamped-Params. |
temperature | conditional | Dropped on models that reject sampling params — which is every current Claude 5 model — and named in X-MindsHub-Dropped-Params. |
top_p | conditional | As temperature. |
top_k | conditional | As temperature. |
stop_sequences | conditional | Forwarded where the transport has a field for them (the OpenAI Responses transport does not). A sequence that fires is reported as stop_reason 'stop_sequence' with the matched string, on Anthropic-wire targets. |
output_config | adapted | Both halves are honoured and travel separately: 'effort' is clamped onto the resolved model's published ladder, and 'format' becomes a schema constraint each provider renders in its own shape. A model that cannot constrain its output at all is a 400, not prose. |
thinking | conditional | Forwarded to models that accept a thinking config, budget_tokens included. Reasoning bills as output tokens. Thinking blocks come back on both streamed and non-streamed turns, so a client can replay them. |
tools | adapted | Client tools (input_schema) translate to every provider, strict included. Anthropic's hosted web_search*/web_fetch* map onto the platform's own web search where the target supports it. Other server tools (computer_, bash_, text_editor_*, code_execution, memory) have no cross-provider meaning and are dropped. |
tool_choice | adapted | auto/any/none/tool all map. disable_parallel_tool_use is honoured, including on transports that spell it as a positive parallel_tool_calls. A forced choice a model cannot honour is downgraded to auto rather than refused (ENG-1095). |
metadata | ignored | Accepted and not read. |
service_tier | ignored | One serving tier; nothing to select. |
cache_control | ignored | The top-level auto-caching form is not read. Block-level cache_control on system blocks, text/tool_result blocks and tool definitions IS honoured on Claude-family targets, and cache usage is reported in the response's usage. |
container | ignored | The code-execution tool is not offered here. |
inference_geo | ignored | Inference geography is a property of the upstream account, not of a request. |
This table is generated from PARAM_SUPPORT in minds/requests/anthropic_compat.py, which a unit test holds to the vendor SDK's own parameter list. The cross-API view is on the capability matrix.
Features the table can only summarize have their own guides: Tool calling (tools, tool_choice, disable_parallel_tool_use), Structured output (output_config.format), Reasoning (output_config.effort, thinking), Web search (web_search* and web_fetch* hosted tools), Prompt caching (block-level cache_control), and Images (image blocks). Server tools (computer_*, bash_*, text_editor_*, code_execution, memory) are dropped from the request; mcp_servers is ignored (Claude Code's own MCP support is client-side and unaffected); Files API references ("source": {"type": "file"}) are unsupported; the Message Batches API is not implemented and POST /v1/messages/batches returns a plain 404.
Unknown top-level request fields are 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.
The cross-API view of every parameter is on the capability matrix.
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.
Request body
The full request and response schemas, with every field described, are on the generated reference pages: POST /v1/messages and POST /v1/messages/count_tokens.
Response
{
"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
}
}
modelechoes back exactly the string you sent (unlike/v1/chat/completions, which reports the resolved ID).contentblocks aretext,tool_use, and, on models that think by default (opus,fable), a leadingthinkingblock with 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.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_sequencewith the matched string instop_sequence, and a refusal carriesstop_details. 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_exceededandcompactionarrive asmax_tokens; treat the response as truncated.pause_turnis never reported, and a paused turn arrives asend_turn, because the server-tool blocks that turn produced are not in the response body, so resuming from it would be incoherent. - All four
usagefields are always present. As in Anthropic's own API,input_tokensexcludes cached tokens: the three input fields partition the prompt.
Streaming events
"stream": true produces the standard Anthropic event sequence (message_start → ping → content_block_* → message_delta → message_stop), with three differences from Anthropic's native stream: ping is sent once, message_start reports input_tokens: 0 on non-Claude models, and a failure after the body opens arrives as an error event. Details in Streaming.
Lane-specific behaviour
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. If Anthropic is unreachable, 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.
Model discovery
GET /v1/models answers in a shape the Anthropic SDK's client.models.list() parses. Each row carries type, display_name, and created_at beside the OpenAI-shaped fields. See Models endpoint.
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. A 429 is typedrate_limit_errorand carries nocodefield, so a client keying retries onerror.codealone will miss it. - 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:).
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.