Skip to main content

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

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)

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

Request parameters

ParameterHandlingNotes
modeladaptedA 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.
messagesforwardedText, 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.
systemforwardedString or content blocks. Blocks pass through verbatim so cache_control markers reach a Claude-family target.
streamforwardedProduces 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_tokensconditionalClamped 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.
temperatureconditionalDropped on models that reject sampling params — which is every current Claude 5 model — and named in X-MindsHub-Dropped-Params.
top_pconditionalAs temperature.
top_kconditionalAs temperature.
stop_sequencesconditionalForwarded 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_configadaptedBoth 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.
thinkingconditionalForwarded 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.
toolsadaptedClient 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_choiceadaptedauto/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).
metadataignoredAccepted and not read.
service_tierignoredOne serving tier; nothing to select.
cache_controlignoredThe 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.
containerignoredThe code-execution tool is not offered here.
inference_geoignoredInference 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 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.

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
}
}
  • model echoes back exactly the string you sent (unlike /v1/chat/completions, which reports the resolved ID).
  • content blocks are text, tool_use, and, on models that think by default (opus, fable), a leading thinking block with 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.
  • 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_sequence with the matched string in stop_sequence, and a refusal carries stop_details. 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 arrive as max_tokens; treat the response as truncated. pause_turn is never reported, and a paused turn arrives as end_turn, because the server-tool blocks that turn produced are not in the response body, so resuming from it would be incoherent.
  • 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 events

"stream": true produces the standard Anthropic event sequence (message_startpingcontent_block_*message_deltamessage_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 typed api_error. Read the HTTP status, not just the type string. A 429 is typed rate_limit_error and carries no code field, so a client keying retries on error.code alone will miss it.
  • 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:).

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.