DocsAPI reference
Messages (Anthropic)
POST /v1/messages and /v1/messages/count_tokens — the Anthropic Messages format with streaming events, thinking blocks and tool use, served by your own open model.
POST https://osarai.xyz/v1/messages
POST https://osarai.xyz/v1/messages/count_tokensOsarai speaks the Anthropic Messages API, so the Anthropic SDKs and Claude Code work against your instance. Requests are translated to your model's chat format and back; the model is still the open model you deployed, not Claude.
Anthropic clients add /v1/messages to the base URL themselves, so give them https://osarai.xyz, without /v1.
Request
curl "https://osarai.xyz/v1/messages" \
-H "x-api-key: $OSARAI_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "qwen3.8-27b",
"max_tokens": 1024,
"system": "You are a concise assistant.",
"messages": [{"role": "user", "content": "What is a KV cache?"}]
}'import anthropic, os
client = anthropic.Anthropic(base_url="https://osarai.xyz", api_key=os.environ["OSARAI_API_KEY"])
message = client.messages.create(
model="qwen3.8-27b",
max_tokens=1024,
system="You are a concise assistant.",
messages=[{"role": "user", "content": "What is a KV cache?"}],
)
print(message.content[-1].text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://osarai.xyz", apiKey: process.env.OSARAI_API_KEY });
const message = await client.messages.create({
model: "qwen3.8-27b",
max_tokens: 1024,
system: "You are a concise assistant.",
messages: [{ role: "user", content: "What is a KV cache?" }],
});
console.log(message.content.at(-1));The key works in x-api-key or in Authorization: Bearer. anthropic-version and anthropic-beta headers are accepted and ignored.
Supported fields
| Field | Notes |
|---|---|
model | Your instance's model id |
messages | user and assistant turns; content as a string or blocks: text, image (base64 or URL), tool_use, tool_result, document (its text) |
system | A string or text blocks |
max_tokens | Answer length cap |
stream | Stream server-sent events |
temperature, top_p, top_k, stop_sequences | Sampling and stop sequences |
tools, tool_choice | Client tools with input_schema; tool_choice auto, any, tool, none, and disable_parallel_tool_use |
thinking | {"type": "disabled"}, {"type": "enabled", "budget_tokens": N} or {"type": "adaptive"} |
output_config.effort | low, medium, high or max, the same as reasoning_effort |
Fields with no counterpart on an open model, such as cache_control, metadata, container and context-management settings, are accepted and ignored, never rejected. Anthropic's server tools (web search, code execution) are dropped from tools, because they run on Anthropic's servers.
Response
{
"id": "msg_3f9a1c0e7b2d4a6f8e1c5b90",
"type": "message",
"role": "assistant",
"model": "qwen3.8-27b",
"content": [
{ "type": "thinking", "thinking": "The user asks what a KV cache is…", "signature": "osarai" },
{ "type": "text", "text": "A KV cache stores the attention keys and values of tokens already processed…" }
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": { "input_tokens": 31, "output_tokens": 214 }
}stop_reason is end_turn, max_tokens, stop_sequence or tool_use.
Streaming
With "stream": true the answer arrives as Anthropic's event sequence:
event: message_start
data: {"type":"message_start","message":{"id":"msg_3f9a1c0e7b2d4a6f8e1c5b90","type":"message","role":"assistant","content":[],"model":"qwen3.8-27b","stop_reason":null,"usage":{"input_tokens":31,"output_tokens":0}}}
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":"A KV cache"}}
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":{"output_tokens":214}}
event: message_stop
data: {"type":"message_stop"}- Thinking streams as a
thinkingblock withthinking_deltaevents, closed by asignature_delta. - Tool calls stream as
tool_useblocks withinput_json_deltaevents. pingevents keep the connection open while the model reads a long prompt.
with client.messages.stream(
model="qwen3.8-27b",
max_tokens=1024,
messages=[{"role": "user", "content": "Count to five."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)Tool use
tools = [{
"name": "get_weather",
"description": "Current weather for a city",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
messages = [{"role": "user", "content": "Weather in Tokyo?"}]
first = client.messages.create(model="qwen3.8-27b", max_tokens=1024, tools=tools, messages=messages)
call = next(b for b in first.content if b.type == "tool_use")
messages.append({"role": "assistant", "content": first.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": call.id, "content": '{"temp_c": 18, "sky": "clear"}'}
]})
final = client.messages.create(model="qwen3.8-27b", max_tokens=1024, tools=tools, messages=messages)
print(final.content[-1].text)Thinking
thinking | Effect on the model |
|---|---|
{"type": "disabled"} | Thinking off (Qwen) or lowest effort (gpt-oss) |
{"type": "enabled", "budget_tokens": 2000} | Low effort (budget under 4,000) |
{"type": "enabled", "budget_tokens": 8000} | Medium effort (under 16,000) |
{"type": "enabled", "budget_tokens": 32000} | High effort |
{"type": "adaptive"} or unset | The model's default |
The budget picks an effort level; it is not a hard cap on thinking tokens. signature fields are placeholders: send thinking blocks back unchanged, as Anthropic clients do.
Counting tokens
curl "https://osarai.xyz/v1/messages/count_tokens" \
-H "x-api-key: $OSARAI_API_KEY" \
-H "content-type: application/json" \
-d '{"model": "qwen3.8-27b", "messages": [{"role": "user", "content": "Hello there"}]}'{ "input_tokens": 12 }The count comes from your instance's own tokenizer, with the same chat template as a real request; if the instance cannot answer, you get an estimate of one token per four characters. Counting does not run the model.
Errors
Errors use Anthropic's shape, {"type": "error", "error": {"type": "…", "message": "…"}}. A prompt longer than the context window answers 400 with prompt is too long: N tokens > M maximum, which Claude Code reads to compact the conversation. See Errors and rate limits.