Anthropic Format
POST /v1/messages takes a request in the Anthropic Messages format and answers in the same format, streamed or not. Use it with the Anthropic SDK and with tools built on it, such as Claude Code: set the base URL, use your Shannon API key and name a Shannon model id.
POST https://api.shannon-ai.com/v1/messages
Point the Anthropic SDK at the Shannon base URL. The SDK adds /v1/messages and sends your key in the x-api-key header.
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com",
)
message = client.messages.create(
model="shannon-3",
max_tokens=1024,
messages=[{"role": "user", "content": "Say hello in one sentence."}],
)
for block in message.content:
if block.type == "text":
print(block.text) import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com",
});
const message = await client.messages.create({
model: "shannon-3",
max_tokens: 1024,
messages: [{ role: "user", content: "Say hello in one sentence." }],
});
for (const block of message.content) {
if (block.type === "text") console.log(block.text);
} curl https://api.shannon-ai.com/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "shannon-3",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Say hello in one sentence."}]
}' The reply is a message whose content is a list of blocks. Find the blocks by their type: when the model reasoned, the first block is thinking and the text comes after it.
{
"id": "msg_5f0c1e7a9b3d4c62a8e1f07d2b46c9a3",
"type": "message",
"role": "assistant",
"model": "shannon-3",
"content": [
{
"type": "thinking",
"thinking": "The user wants a greeting in one sentence. Keep it short and friendly."
},
{
"type": "text",
"text": "Hello, it is good to meet you."
}
],
"stop_reason": "end_turn",
"usage": {
"input_tokens": 1184,
"output_tokens": 46
}
} Headers
| Header | Value | Description |
|---|---|---|
x-api-key | YOUR_API_KEY | Your API key. Authorization: Bearer YOUR_API_KEY is accepted in its place. |
content-type | application/json | Required. |
anthropic-version | 2023-06-01 | Accepted, not required. The Anthropic SDK sends 2023-06-01; the reply has the same format whatever the value. |
anthropic-beta | Accepted, not required. | |
x-request-id | Optional. Your own id for the request. It comes back unchanged on the reply; without it the reply carries an id of 12 hexadecimal characters. |
Request fields
Only messages is required. The Applied by column names the models on which a field changes the reply. The hosted open-weight models are the twelve ids of the model list; the Shannon 3 family is shannon-3, shannon-3-pro, shannon-3.1 and shannon-3.1-pro. Models & pricing
| Field | Type | Default | Description | Applied by |
|---|---|---|---|---|
model | string | shannon-1.6-lite | A Shannon model id from the model list. Send it with every request. Matching is not case-sensitive. Any other name returns 400 unknown model, so a tool that sends its own model names has to be set to a Shannon id. | All models |
messages | array | Required. The conversation, oldest message first, with the roles user and assistant. See Messages and content blocks below. | All models | |
system | string | array | Instructions for the model: a string, or an array of text blocks. See System prompt below. | Hosted open-weight models, shannon-1.6-*, shannon-2-*, shannon-coder-1 | |
max_tokens | integer | 4096 | Upper limit of the reply, in tokens. Optional on this endpoint; the Anthropic SDK always sends it. A value outside 1 to 65,536 is moved into that range. It is also the amount set aside from your balance while the request runs. | Hosted open-weight models, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 |
stream | boolean | false | true sends the reply as server-sent events. See Streaming below. | All models |
temperature | number | Sampling temperature. On the hosted open-weight models values are kept between 0 and 2. | Hosted open-weight models, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | |
top_p | number | Nucleus sampling. | Hosted open-weight models | |
stop_sequences | array | Up to 4 strings. The answer ends before the first one that appears. stop_reason stays end_turn. | Hosted open-weight models | |
tools | array | The tools the model may call, each as {"name", "description", "input_schema"}. See Tools below. | All models | |
tool_choice | object | {"type": "auto"} lets the model decide, {"type": "any"} makes it call a tool, {"type": "tool", "name": "…"} makes it call that tool. | Hosted open-weight models | |
thinking | object | How much the model reasons before it answers. See Thinking below. | Hosted open-weight models | |
web_search | boolean | false | A Shannon addition to the format: true lets the model search the web before it answers. | shannon-1.6-*, shannon-2-*, Shannon 3 family |
Without max_tokens the value is 4,096, and 65,536 on shannon-coder-1.
metadata, top_k, service_tier and cache_control markers on blocks are accepted so that existing client code runs unchanged. They do not change the reply; prompt caching on the hosted open-weight models is automatic.
A JSON answer to a schema is asked with response_format on Chat Completions and with text.format on Responses. Structured Outputs
A request with a system prompt and thinking
This request sends a system prompt, a thinking budget, a temperature and a stop sequence. It uses a hosted open-weight model, which applies all of them.
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com",
)
message = client.messages.create(
model="DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
max_tokens=4096,
system="You are a physics teacher. Answer in two sentences.",
thinking={"type": "enabled", "budget_tokens": 2048},
temperature=0.3,
stop_sequences=["\n\n"],
messages=[{"role": "user", "content": "Why is the sky blue?"}],
)
for block in message.content:
if block.type == "thinking":
print("reasoning:", block.thinking)
elif block.type == "text":
print("answer:", block.text)
print(message.usage) import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com",
});
const message = await client.messages.create({
model: "DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
max_tokens: 4096,
system: "You are a physics teacher. Answer in two sentences.",
thinking: { type: "enabled", budget_tokens: 2048 },
temperature: 0.3,
stop_sequences: ["\n\n"],
messages: [{ role: "user", content: "Why is the sky blue?" }],
});
for (const block of message.content) {
if (block.type === "thinking") console.log("reasoning:", block.thinking);
if (block.type === "text") console.log("answer:", block.text);
}
console.log(message.usage); curl https://api.shannon-ai.com/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
"max_tokens": 4096,
"system": "You are a physics teacher. Answer in two sentences.",
"thinking": {"type": "enabled", "budget_tokens": 2048},
"temperature": 0.3,
"stop_sequences": ["\n\n"],
"messages": [{"role": "user", "content": "Why is the sky blue?"}]
}' On the hosted open-weight models usage reports the tokens read from the prompt cache apart from the other input tokens.
{
"usage": {
"input_tokens": 31,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0,
"output_tokens": 62
}
} System prompt
system is a string, or an array of text blocks in the form [{"type": "text", "text": "…"}]. The texts of the blocks are joined with a line break.
It is applied by the hosted open-weight models, shannon-1.6-lite, shannon-1.6-pro, shannon-2-lite, shannon-2-pro and shannon-coder-1. With a Shannon 3 family id, put instructions that must hold into the user message.
Messages and content blocks
A message has a role, user or assistant, and a content. content is a string, or an array of blocks.
| Block | Description | Available on |
|---|---|---|
{"type": "text", "text": "…"} | Text. | All models |
{"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "…"}}{"type": "image", "source": {"type": "url", "url": "…"}} | An image in a user message, as base64 or by URL. | Shannon 3 family, shannon-1.6-lite, shannon-1.6-pro, and the hosted open-weight models that list image input |
{"type": "document", "source": {"type": "base64", "media_type": "application/pdf", "data": "…"}} | A document (PDF, Word, PowerPoint or Excel) in a user message, as base64 or by URL. | Shannon 3 family |
{"type": "tool_use", "id": "toolu_…", "name": "…", "input": {…}} | In an assistant message: a tool call the model made in an earlier reply. | All models |
{"type": "tool_result", "tool_use_id": "toolu_…", "content": "…"} | In a user message: the result of that call. content is a string, or a list of text blocks. | All models |
You can send the content of an earlier reply back as it is: text and tool_use blocks are read, thinking blocks are skipped.
On the Shannon tiers images and documents are read from the last user message. Sizes and limits have their own page. Images and files
Thinking
The hosted open-weight models map thinking to a reasoning effort. Without the field the effort is high.
| Form | budget_tokens | Effect |
|---|---|---|
{"type": "disabled"} | No reasoning. The reply has no thinking block. | |
{"type": "enabled", "budget_tokens": 2048} | ≤ 2,048 | Low effort. |
{"type": "enabled", "budget_tokens": 8192} | ≤ 8,192 | Medium effort. |
{"type": "enabled", "budget_tokens": 16000} | > 8,192 | High effort. |
A model that reasons returns its reasoning as a block {"type": "thinking", "thinking": "…"} in front of the text. The Shannon tiers that reason send it as well.
The efforts are described on their own page. Reasoning effort
Tools
A tool is a function your code runs. Describe each one with a name, a description and an input_schema. When the model wants a tool, the reply holds a tool_use block and stop_reason is tool_use.
A tool definition in tools:
[
{
"name": "get_weather",
"description": "Current weather for a city.",
"input_schema": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": [
"city"
]
}
}
] The content of a reply that calls it:
[
{
"type": "text",
"text": ""
},
{
"type": "tool_use",
"id": "toolu_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"name": "get_weather",
"input": {
"city": "Paris"
}
}
] Run the tool, then send the request again with the call and a tool_result block added to messages:
[
{
"role": "user",
"content": "What is the weather in Paris?"
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"name": "get_weather",
"input": {
"city": "Paris"
}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_3d9a7c1e5b2f4a60c8e1d7f09b24a6c5",
"content": "{\"temperature_c\": 18, \"sky\": \"cloudy\"}"
}
]
}
] tool_choice
| Form | Effect | Applied by |
|---|---|---|
{"type": "auto"} | The model decides whether to call a tool. This is the default. | All models |
{"type": "any"} | The model must call one of the tools. | Hosted open-weight models |
{"type": "tool", "name": "…"} | The model must call the named tool. | Hosted open-weight models |
Only tools with an input_schema are read. Web search is not a tool on this endpoint: set the web_search field.
The tool loop is described in full on its own page. Function Calling
The reply
| Field | Type | Description | Sent by |
|---|---|---|---|
id | string | msg_ followed by 32 hexadecimal characters. | All models |
type | string | Always message. | All models |
role | string | Always assistant. | All models |
model | string | The canonical id of the model that answered. | All models |
content | array | The blocks of the reply, in this order: thinking when the model reasoned, text, then one tool_use per tool call. | All models |
stop_reason | string | Why the reply ended. See the values below. | All models |
usage | object | input_tokens and output_tokens. | All models |
sources | array | Only on a request with web_search: true whose search found something: the results the model was given, each with index, title and url. [1] in the answer is the entry with index 1. In a stream it comes with the message_delta event. | shannon-1.6-*, shannon-2-*, Shannon 3 family |
usage.cache_read_input_tokens | integer | cache_read_input_tokens is the part of the prompt read from cache; input_tokens is the rest. cache_creation_input_tokens is always 0. | Hosted open-weight models |
Reply blocks
| Block | Description |
|---|---|
{"type": "thinking", "thinking": "…"} | The model's reasoning. There is no signature member. |
{"type": "text", "text": "…"} | The answer text. It can be empty when the reply is a tool call. |
{"type": "tool_use", "id": "toolu_…", "name": "…", "input": {…}} | A tool call. input is a JSON object. Send the result back with the same id. |
stop_reason
| stop_reason | Description | Sent by |
|---|---|---|
end_turn | The model finished its answer, or one of your stop_sequences appeared. | All models |
tool_use | The reply holds one or more tool calls. | All models |
max_tokens | The reply was cut at the output limit. | Shannon 3 family, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1, in streams |
Streaming
Set stream to true for server-sent events. Each frame has an event: line with the name of the event and a data: line with a JSON object that repeats the name in type. The stream helpers of the Anthropic SDK read these events for you:
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com",
)
with client.messages.stream(
model="shannon-3",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain server-sent events in three sentences."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)
message = stream.get_final_message()
print()
print(message.stop_reason, message.usage) import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com",
});
const stream = client.messages.stream({
model: "shannon-3",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain server-sent events in three sentences." }],
});
stream.on("text", (text) => process.stdout.write(text));
const message = await stream.finalMessage();
console.log("\n", message.stop_reason, message.usage); curl -N https://api.shannon-ai.com/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "shannon-3",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "Explain server-sent events in three sentences."}]
}' The events, in the order they arrive:
| Event | Data | When |
|---|---|---|
message_start | message | Always first. message holds the id, the role, the model, an empty content and a usage that starts at zero. |
content_block_start | index, content_block | A block opens. content_block is an empty text or thinking block, or a tool_use block with its id and name. |
content_block_delta | index, delta {"type": "thinking_delta", "thinking"} | A piece of the reasoning, for a thinking block. |
content_block_delta | index, delta {"type": "text_delta", "text"} | A piece of the answer, for a text block. |
content_block_delta | index, delta {"type": "input_json_delta", "partial_json"} | The arguments of a tool call, complete in one delta, for a tool_use block. |
content_block_stop | index | The block with this index is complete. |
message_delta | delta {stop_reason, stop_sequence}, usage | The end of the reply: delta.stop_reason, and the usage of the request. |
message_stop | Always last. |
Blocks are numbered by index in the order they open, and one block is complete before the next one opens. Read each block by its type, not by its number: a stream can open an empty text block first, then the thinking block, and deliver the answer in a later text block.
event: message_start
data: {"type":"message_start","message":{"id":"msg_5f0c…","type":"message","role":"assistant","model":"shannon-3","content":[],"usage":{"input_tokens":0,"output_tokens":0}}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"thinking","thinking":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"thinking_delta","thinking":"Three sentences: what it is, how it is framed, why it is used."}}
event: ping
data: {"type": "ping"}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
event: content_block_start
data: {"type":"content_block_start","index":2,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":2,"delta":{"type":"text_delta","text":"Server-sent events let a server push text over one HTTP reply."}}
event: content_block_stop
data: {"type":"content_block_stop","index":2}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":1190,"output_tokens":84}}
event: message_stop
data: {"type":"message_stop"} Keep-alive
| Sent by | Description |
|---|---|
Shannon 3 family, shannon-1.6-lite, shannon-1.6-pro, shannon-coder-1 | A ping event with the data {"type": "ping"}, every 15 seconds. |
| Hosted open-weight models | A comment line : keepalive, every 15 seconds while the model works. |
A stream that fails
A failure after the stream has started is an error event with the same body as an error reply: {"type": "error", "error": {"type", "message"}}. The closing events, message_delta and message_stop, still follow. Treat the reply as failed and send the request again.
Errors
Errors use the Anthropic error body: type is error, and the error object holds a type and a message. The table lists what this endpoint returns most often. The full list, with what to retry, has its own page. Error Handling
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} | Status | Type | Message | When |
|---|---|---|---|
401 | authentication_error | Missing authenticationInvalid API key | No API key was sent, or the key is unknown or revoked. |
400 | invalid_request_error | unknown model: <id> | model is not a Shannon model id. |
400 | invalid_request_error | No user message provided | Shannon tiers: the request has no user text and no tools. |
400 | invalid_request_error | <id> does not accept image input | An image was sent to a hosted open-weight model without image input. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | messages is missing, or a field has the wrong JSON type. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | max_tokens is larger than what is left of your balance. |
429 | rate_limit_error | Too many requests. Retry in <n>s. | Flood protection: more than 120 requests in one minute on your account. |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | shannon-coder-1: your plan's Shannon Coder calls for the current 4-hour window are used. |
500 | server_error | The model backend failed to answer. Please retry. | The model did not produce a reply. Send the request again. |
502 | api_error | The model backend failed to answer. Please retry. | The model did not produce a reply. Send the request again. |
Counting tokens
POST /v1/messages/count_tokens takes the same body as a message request (model, system, messages, tools) and returns {"input_tokens": n}: the number of input tokens the request would be charged. It counts for the hosted open-weight models, it is free and it is not counted by flood protection.
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com",
)
count = client.messages.count_tokens(
model="DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
system="You are a physics teacher. Answer in two sentences.",
messages=[{"role": "user", "content": "Why is the sky blue?"}],
)
print(count.input_tokens) import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com",
});
const count = await client.messages.countTokens({
model: "DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
system: "You are a physics teacher. Answer in two sentences.",
messages: [{ role: "user", content: "Why is the sky blue?" }],
});
console.log(count.input_tokens); curl https://api.shannon-ai.com/v1/messages/count_tokens \
-H "x-api-key: YOUR_API_KEY" \
-H "content-type: application/json" \
-d '{
"model": "DeepSeek-V4-Flash-0731-W4A16-AUTOROUND-REAP",
"system": "You are a physics teacher. Answer in two sentences.",
"messages": [{"role": "user", "content": "Why is the sky blue?"}]
}'
# {"input_tokens": 31} Counting has its own page, with the plain-text endpoint and the limits. Token counting