Skip to content

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/messages

Authentication

The Anthropic-compatible endpoint supports both authentication methods:

http
x-api-key: sk-sb-your-api-key

or

http
Authorization: Bearer sk-sb-your-api-key

When both are present, x-api-key takes priority (matching Anthropic SDK behavior).

Optional Headers

HeaderRequiredDescription
anthropic-versionNoVersion string (e.g., 2023-06-01). Accepted but not enforced.
Content-TypeYesMust be application/json

Request Body

The request body follows the Anthropic Messages API specification:

ParameterTypeRequiredDefaultDescription
modelstringYesModel identifier (e.g., claude-sonnet-4-20250514, anthropic/claude-sonnet-4-20250514)
messagesarrayYesConversation messages array
systemstring or arrayNoSystem prompt
max_tokensintegerYesMaximum tokens to generate (required per Anthropic spec)
temperaturenumberNo1.0Sampling temperature (0.0–1.0)
top_pnumberNoNucleus sampling
top_kintegerNoTop-K sampling
stop_sequencesstring[]NoCustom stop sequences
streambooleanNofalseEnable streaming via SSE
metadataobjectNoRequest metadata (e.g., user_id)
toolsarrayNoTool definitions for function calling
tool_choiceobjectNoTool selection behavior
thinkingobjectNoExtended thinking configuration

Messages Array

Each message has a role and content:

json
{
  "role": "user",
  "content": "What is the capital of France?"
}

Content can also be an array of content blocks:

json
{
  "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:

json
{
  "system": "You are a helpful coding assistant."
}

or

json
{
  "system": [
    { "type": "text", "text": "You are a helpful coding assistant." }
  ]
}

Tools

json
{
  "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

ValueBehavior
{"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:

json
{
  "thinking": {
    "type": "enabled",
    "budget_tokens": 10000
  }
}

Response (Non-Streaming)

json
{
  "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

FieldTypeDescription
idstringUnique message identifier (prefixed with msg_)
typestringAlways "message"
rolestringAlways "assistant"
contentarrayArray of content blocks
modelstringModel used for generation
stop_reasonstringWhy generation stopped
stop_sequencestring or nullThe stop sequence that was hit, if any
usageobjectToken usage statistics

Content Block Types

TypeDescription
textText content: {"type": "text", "text": "..."}
tool_useTool invocation: {"type": "tool_use", "id": "...", "name": "...", "input": {...}}
thinkingThinking content (when extended thinking is enabled)

Stop Reasons

ValueDescription
end_turnNatural end of response
max_tokensHit the max_tokens limit
stop_sequenceHit a custom stop sequence
tool_useModel invoked a tool

Response (Streaming)

When stream: true, the response uses Anthropic's SSE event protocol:

Content-Type: text/event-stream

Event 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

EventDescription
message_startStream begins, includes message metadata and input token count
content_block_startA new content block begins
content_block_deltaIncremental content within a block
content_block_stopA content block is complete
message_deltaFinal message metadata (stop_reason, output usage)
message_stopStream is complete
pingHeartbeat (sent every ~15s to keep connection alive)

Delta Types

Delta TypeParent EventDescription
text_deltacontent_block_deltaText content increment
input_json_deltacontent_block_deltaTool input JSON increment
thinking_deltacontent_block_deltaThinking content increment

Differences from Native Anthropic API

SandBase's Anthropic-compatible endpoint is designed for SDK-level compatibility. Key differences:

AspectSandBaseNative Anthropic
Base URLhttps://api.sandbase.aihttps://api.anthropic.com
API Key prefixsk-sb-sk-ant-
Model routingRoutes through SandBase (may use OpenRouter as backend)Direct to Anthropic
anthropic-version headerAccepted but not enforcedRequired
Batch API (/v1/messages/batches)Not supportedSupported
cache_controlAccepted (may not take effect depending on routing)Fully supported
Image blocksSupported (vision-capable models)Supported
Document blocksNot yet supportedSupported

Error Format

Errors follow the Anthropic error structure:

json
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "max_tokens is required"
  }
}
HTTP StatusError TypeDescription
400invalid_request_errorMalformed request or unsupported feature
401authentication_errorMissing or invalid API key
402invalid_request_errorInsufficient account balance
404not_found_errorModel not found or not enabled
429rate_limit_errorRate limit exceeded
500api_errorInternal server error
502api_errorUpstream provider error

Code Examples

Basic Message

bash
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."}
    ]
  }'
python
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)
javascript
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

bash
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."}
    ]
  }'
python
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)
javascript
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

python
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}")
javascript
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

python
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}")
javascript
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}`);
  }
}