Anthropic Messages API
SandBase exposes an Anthropic-compatible Messages endpoint. Applications built with the @anthropic-ai/sdk (JavaScript) or anthropic (Python) SDK can connect to SandBase by changing the base URL — no other code changes required.
Endpoint
POST https://api.sandbase.ai/v1/messagesAuthentication
The Anthropic-compatible endpoint supports both authentication methods:
x-api-key: sk-sb-your-api-keyor
Authorization: Bearer sk-sb-your-api-keyWhen both are present, x-api-key takes priority (matching Anthropic SDK behavior).
Optional Headers
| Header | Required | Description |
|---|---|---|
anthropic-version | No | Version string (e.g., 2023-06-01). Accepted but not enforced. |
Content-Type | Yes | Must be application/json |
Request Body
The request body follows the Anthropic Messages API specification:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
model | string | Yes | — | Model identifier (e.g., claude-sonnet-4-20250514, anthropic/claude-sonnet-4-20250514) |
messages | array | Yes | — | Conversation messages array |
system | string or array | No | — | System prompt |
max_tokens | integer | Yes | — | Maximum tokens to generate (required per Anthropic spec) |
temperature | number | No | 1.0 | Sampling temperature (0.0–1.0) |
top_p | number | No | — | Nucleus sampling |
top_k | integer | No | — | Top-K sampling |
stop_sequences | string[] | No | — | Custom stop sequences |
stream | boolean | No | false | Enable streaming via SSE |
metadata | object | No | — | Request metadata (e.g., user_id) |
tools | array | No | — | Tool definitions for function calling |
tool_choice | object | No | — | Tool selection behavior |
thinking | object | No | — | Extended thinking configuration |
Messages Array
Each message has a role and content:
{
"role": "user",
"content": "What is the capital of France?"
}Content can also be an array of content blocks:
{
"role": "user",
"content": [
{ "type": "text", "text": "Describe this image:" },
{ "type": "image", "source": { "type": "url", "url": "https://example.com/photo.jpg" } }
]
}System Prompt
The system field can be a string or an array of content blocks:
{
"system": "You are a helpful coding assistant."
}or
{
"system": [
{ "type": "text", "text": "You are a helpful coding assistant." }
]
}Tools
{
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a location",
"input_schema": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "City name" }
},
"required": ["location"]
}
}
]
}Tool Choice
| Value | Behavior |
|---|---|
{"type": "auto"} | Model decides whether to use tools |
{"type": "any"} | Model must use at least one tool |
{"type": "tool", "name": "..."} | Force a specific tool |
Thinking (Extended Reasoning)
Enable extended thinking for supported models:
{
"thinking": {
"type": "enabled",
"budget_tokens": 10000
}
}Response (Non-Streaming)
{
"id": "msg_01XFDUDYJgAACzvnptvVoYEL",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "The capital of France is Paris."
}
],
"model": "claude-sonnet-4-20250514",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 14,
"output_tokens": 10
}
}Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique message identifier (prefixed with msg_) |
type | string | Always "message" |
role | string | Always "assistant" |
content | array | Array of content blocks |
model | string | Model used for generation |
stop_reason | string | Why generation stopped |
stop_sequence | string or null | The stop sequence that was hit, if any |
usage | object | Token usage statistics |
Content Block Types
| Type | Description |
|---|---|
text | Text content: {"type": "text", "text": "..."} |
tool_use | Tool invocation: {"type": "tool_use", "id": "...", "name": "...", "input": {...}} |
thinking | Thinking content (when extended thinking is enabled) |
Stop Reasons
| Value | Description |
|---|---|
end_turn | Natural end of response |
max_tokens | Hit the max_tokens limit |
stop_sequence | Hit a custom stop sequence |
tool_use | Model invoked a tool |
Response (Streaming)
When stream: true, the response uses Anthropic's SSE event protocol:
Content-Type: text/event-streamEvent Sequence
Events are delivered in strict order:
event: message_start
data: {"type":"message_start","message":{"id":"msg_01...","type":"message","role":"assistant","content":[],"model":"claude-sonnet-4-20250514","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":14,"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":"The capital"}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":" of France is Paris."}}
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":10}}
event: message_stop
data: {"type":"message_stop"}Event Types
| Event | Description |
|---|---|
message_start | Stream begins, includes message metadata and input token count |
content_block_start | A new content block begins |
content_block_delta | Incremental content within a block |
content_block_stop | A content block is complete |
message_delta | Final message metadata (stop_reason, output usage) |
message_stop | Stream is complete |
ping | Heartbeat (sent every ~15s to keep connection alive) |
Delta Types
| Delta Type | Parent Event | Description |
|---|---|---|
text_delta | content_block_delta | Text content increment |
input_json_delta | content_block_delta | Tool input JSON increment |
thinking_delta | content_block_delta | Thinking content increment |
Differences from Native Anthropic API
SandBase's Anthropic-compatible endpoint is designed for SDK-level compatibility. Key differences:
| Aspect | SandBase | Native Anthropic |
|---|---|---|
| Base URL | https://api.sandbase.ai | https://api.anthropic.com |
| API Key prefix | sk-sb- | sk-ant- |
| Model routing | Routes through SandBase (may use OpenRouter as backend) | Direct to Anthropic |
anthropic-version header | Accepted but not enforced | Required |
Batch API (/v1/messages/batches) | Not supported | Supported |
cache_control | Accepted (may not take effect depending on routing) | Fully supported |
| Image blocks | Supported (vision-capable models) | Supported |
| Document blocks | Not yet supported | Supported |
Error Format
Errors follow the Anthropic error structure:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens is required"
}
}| HTTP Status | Error Type | Description |
|---|---|---|
| 400 | invalid_request_error | Malformed request or unsupported feature |
| 401 | authentication_error | Missing or invalid API key |
| 402 | invalid_request_error | Insufficient account balance |
| 404 | not_found_error | Model not found or not enabled |
| 429 | rate_limit_error | Rate limit exceeded |
| 500 | api_error | Internal server error |
| 502 | api_error | Upstream provider error |
Code Examples
Basic Message
curl https://api.sandbase.ai/v1/messages \
-H "x-api-key: sk-sb-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Explain quantum computing in simple terms."}
]
}'from anthropic import Anthropic
client = Anthropic(
api_key="sk-sb-your-key",
base_url="https://api.sandbase.ai"
)
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": "Explain quantum computing in simple terms."}
]
)
print(message.content[0].text)import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: 'sk-sb-your-key',
baseURL: 'https://api.sandbase.ai',
});
const message = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Explain quantum computing in simple terms.' },
],
});
console.log(message.content[0].text);Streaming
curl https://api.sandbase.ai/v1/messages \
-H "x-api-key: sk-sb-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-20250514",
"max_tokens": 1024,
"stream": true,
"messages": [
{"role": "user", "content": "Write a short poem about the ocean."}
]
}'from anthropic import Anthropic
client = Anthropic(
api_key="sk-sb-your-key",
base_url="https://api.sandbase.ai"
)
with client.messages.stream(
model="claude-sonnet-4-20250514",
max_tokens=1024,
messages=[
{"role": "user", "content": "Write a short poem about the ocean."}
]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: 'sk-sb-your-key',
baseURL: 'https://api.sandbase.ai',
});
const stream = client.messages.stream({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Write a short poem about the ocean.' },
],
});
for await (const event of stream) {
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
process.stdout.write(event.delta.text);
}
}With System Prompt and Tools
from anthropic import Anthropic
client = Anthropic(
api_key="sk-sb-your-key",
base_url="https://api.sandbase.ai"
)
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=1024,
system="You are a helpful assistant that can look up weather information.",
messages=[
{"role": "user", "content": "What's the weather like in San Francisco?"}
],
tools=[
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"]
}
}
]
)
# Check if the model wants to use a tool
for block in message.content:
if block.type == "tool_use":
print(f"Tool: {block.name}")
print(f"Input: {block.input}")import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: 'sk-sb-your-key',
baseURL: 'https://api.sandbase.ai',
});
const message = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
system: 'You are a helpful assistant that can look up weather information.',
messages: [
{ role: 'user', content: "What's the weather like in San Francisco?" },
],
tools: [
{
name: 'get_weather',
description: 'Get the current weather in a given location',
input_schema: {
type: 'object',
properties: {
location: {
type: 'string',
description: 'The city and state, e.g. San Francisco, CA',
},
},
required: ['location'],
},
},
],
});
for (const block of message.content) {
if (block.type === 'tool_use') {
console.log(`Tool: ${block.name}`);
console.log(`Input: ${JSON.stringify(block.input)}`);
}
}Extended Thinking
from anthropic import Anthropic
client = Anthropic(
api_key="sk-sb-your-key",
base_url="https://api.sandbase.ai"
)
message = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=16000,
thinking={
"type": "enabled",
"budget_tokens": 10000
},
messages=[
{"role": "user", "content": "Solve this step by step: What is 127 * 389?"}
]
)
for block in message.content:
if block.type == "thinking":
print(f"Thinking: {block.thinking}")
elif block.type == "text":
print(f"Answer: {block.text}")import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: 'sk-sb-your-key',
baseURL: 'https://api.sandbase.ai',
});
const message = await client.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 16000,
thinking: {
type: 'enabled',
budget_tokens: 10000,
},
messages: [
{ role: 'user', content: 'Solve this step by step: What is 127 * 389?' },
],
});
for (const block of message.content) {
if (block.type === 'thinking') {
console.log(`Thinking: ${block.thinking}`);
} else if (block.type === 'text') {
console.log(`Answer: ${block.text}`);
}
}
