Messages
POST https://api-staging.impossiblecarrot.cc/api/v1/messages
Send an Anthropic Messages request to a marketplace model and receive a message object or
an Anthropic event stream. Requires bearer authentication: send
Authorization: Bearer or x-api-key carrying a Heimdallm API key — the header the Anthropic
SDKs and the Claude Code CLI send when configured with ANTHROPIC_API_KEY. Sending both headers
with different values is refused. Choose a model id from the catalogue; the
API dialect does not select Claude or guarantee a particular model's capabilities.
Request body
| Field | Type | Notes |
|---|---|---|
model | string | Required marketplace model id; routing suffixes :free, :nitro and :floor are accepted. |
messages | object[] | Required, at least one user, assistant or system turn. content is a string or an array of blocks. |
max_tokens | positive integer | Required ceiling on generated tokens. |
system | string or object[] | System prompt, or an array of text blocks. |
tools | object[] | Function definitions with name, optional description and input_schema. Hosted web search is refused. |
tool_choice | object | {"type":"auto"}, {"type":"any"}, {"type":"none"} or {"type":"tool","name":"get_weather"}. any requires a tool call. |
stop_sequences | string[] | Up to eight stop sequences. |
temperature, top_p, top_k | number | Respectively 0–2, 0–1 and a nonnegative integer; support depends on the endpoint. |
stream | boolean | Set to true for server-sent events. |
metadata, thinking | object | Accepted without effect. thinking does not control the routed model's reasoning. |
A system turn placed among the messages is accepted, and its text joins the top-level system text in
the one leading system message sent upstream: a chat template takes a system turn only as the opening
message, so a briefing sent mid-conversation reaches the model as part of the system prompt.
Text blocks use {"type":"text","text":"Hello."}. User image blocks accept a source with
type: "base64", media_type and data, or type: "url" and url; choose a model that reads
images. User document blocks accept the same base64 and url sources for a PDF, or a text
source that joins the turn as text; choose a model that reads files. An image or document block
may instead carry source { "type": "file", "file_id": "…" }, naming a file stored with the
Files API; see Documents & files. An assistant tool call is a tool_use block with id, name and input. Return its result
in the next user turn as a tool_result block with the matching tool_use_id and content.
content may hold text, image and document blocks. A returned image or PDF reaches the model as
an image or file part in the user turn after the results, labelled with its tool_use_id, so the
request is routed only to an endpoint that reads that input, and a model with none answers HTTP 503
naming it. A plain-text document stays in the result's text; a block of any other type is named there
and its payload left out.
Your client executes the function; the marketplace does not execute it.
Example
Replace the example model with one listed in the catalogue. Token counts and ids in the reply are illustrative.
curl https://api-staging.impossiblecarrot.cc/api/v1/messages \
-H "Authorization: Bearer $HEIMDALL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4-flash-0731-abliterated",
"max_tokens": 64,
"messages": [{ "role": "user", "content": "Say hello." }]
}'
{
"id": "msg_gen-example",
"type": "message",
"role": "assistant",
"model": "deepseek/deepseek-v4-flash-0731-abliterated",
"content": [{ "type": "text", "text": "Hello." }],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 9, "output_tokens": 3 }
}
A picture the model generates is an image block beside the text, with a base64 source carrying
the bytes and their media_type, or a url source where the endpoint answered with a URL of its
own. Generated audio has no block on this wire: a reply carrying it is refused with HTTP 422 naming
the limit, and the same model's speech is served on
POST /chat/completions, whose reply carries it in message.audio.
content holds text and/or tool_use blocks. stop_reason is end_turn, max_tokens or
tool_use; the translator returns stop_sequence: null, including when a stop sequence was
requested. usage carries input and output token counts, without usage.cost or cache breakdowns.
This dialect's usage object is required and states both counts, and a client parsing it rejects an
object with a member missing, so a count the endpoint serving the reply never reported has no member
to leave out. It is sent as 0 and named in usage.heimdall.unreported, a marketplace extension
listing every count whose zero nothing measured:
"usage": { "input_tokens": 0, "output_tokens": 3, "heimdall": { "unreported": ["input_tokens"] } }
A count not named there is the endpoint's own figure, so a zero without the marker is a measured zero. The marker is absent from a reply whose counts were both reported, and a reply the endpoint sent no usage document for names both. Clients that do not know the extension read the object as before.
Streaming
Add "stream": true to the same body and use curl -N. The response is text/event-stream.
Events have both an event: name and a JSON type. A text answer follows this sequence
(the example shows the content and closing events after message_start and ping):
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":9,"output_tokens":3}}
event: message_stop
data: {"type":"message_stop"}
A generated picture opens as a content_block_start whose content_block is the whole image
block, followed at once by its content_block_stop; there is no image delta. A stream that produces
audio ends with the same refusal as an error event, because the status is already sent.
message_start contains an empty message whose token counts are both zero and both named in
usage.heimdall.unreported, because nothing is measured when the stream opens. Read through the final
message_delta for usage; those initial zeros are not measurements. Tool blocks open with
content_block_start and type: "tool_use"; their arguments arrive as input_json_delta with
partial_json fragments. Accumulate the fragments before parsing them. ping events and SSE
comments can occur between content events. Success ends with message_stop, without [DONE].
Authentication and body validation failures use the HTTP error envelope.
The stream commits on its first event, or after one keep-alive period when the model is slower than
that to start, so a rejection before then (an unknown model, no eligible endpoint, an engine refusing
a request member) is an HTTP error status with
{"type":"error","error":{"type":"invalid_request_error","message":"…","metadata":{…}}}.
Once streaming begins, a failure arrives as event: error in the same shape (the error type
varies), and the connection closes. A 503 the request did not cause, a marketplace restart among
them, is overloaded_error: the type Anthropic clients retry. metadata is the public error envelope's and
carries the engine's own diagnostic as provider_message when one came back. A partial answer without message_stop is not a completed message.
Web search
The marketplace runs no web search. Anthropic's hosted search is declared in tools by a dated type
(web_search_20250305 and later) and run by Anthropic's own API, which reports it as server_tool_use
and web_search_tool_result blocks and counts it in usage.server_tool_use.web_search_requests. A
request that declares one is refused with HTTP 400 before inference, naming the entry (tools[0]) and
its version, because a model offered a search nothing can run answers as though it had searched.
A reply never carries those blocks, a citations member on a text block or a usage.server_tool_use
member, so a client counting from them reads none, which is the number the marketplace ran. Claude Code's
built-in WebSearch tool declares the hosted search and fails with this refusal; search through an MCP
server or another tool your client runs.
To search, declare a function of your own, such as web_search with an input_schema taking a query,
and run it in your client. Each call the model makes is one tool_use block; several in one turn arrive
as several blocks with their own id, and the next user turn answers each with a tool_result whose
is_error is true where that search failed. A search that found nothing is a result, not an error.
Hosted-search blocks from an earlier Anthropic reply are not forwarded; the model reads the
conversation's text.
Heimdallm differences
The request runs through the same model routing as chat completions,
session affinity included: send X-Session-Id to keep
a conversation on the endpoint that served it. The Anthropic wire carries no conversation id of its
own, so this dialect has no other way to take part.
X-Generation-Id identifies the generation; a successful non-streaming reply also carries
X-Provider-Name. Read the generation's recorded cost through
GET /generation using that header. These dialect replies
contain neither a provider field nor a usage.cost field.
The translation handles text, images and function tools. Other content blocks and block hints
such as cache_control are not forwarded; accepting them does not enable Anthropic-specific
features. models, provider and route carry the shared routing controls; other extra request members are not forwarded.
Key routing defaults apply below explicit request preferences. Tools require an eligible endpoint that supports them.
For a context estimate, use Count message tokens. For harness integration, see Switching to Heimdallm.