Anthropic Messages API
POST https://api.bve.me/v1/messagesGET https://api.bve.me/v1/messages/:message_idRequires Authorization: Bearer sk-bve-YOUR_KEY.
BVE Gateway natively passes through the Anthropic Messages API. The response is in Anthropic format (not OpenAI format). This is useful when you want to use Anthropic-specific features like extended thinking or when targeting Claude models directly.
Request body
Section titled “Request body”{ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Hello, Claude!" } ]}| Field | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Claude model ID (e.g. claude-sonnet-4-6) |
messages | array | Yes | Array of {role, content} objects |
max_tokens | integer | Yes | Maximum tokens to generate |
system | string or array | No | System prompt. May be a plain string or an Anthropic content-block array. |
temperature | number | No | Sampling temperature 0–1 |
top_p | number | No | Nucleus sampling |
top_k | integer | No | Top-k sampling |
stream | boolean | No | Enable SSE streaming |
stop_sequences | array | No | Stop sequences |
tools | array | No | Tool definitions |
tool_choice | object | No | Tool selection strategy |
thinking | object | No | Extended thinking configuration |
betas | array | No | Anthropic beta feature flags — e.g. ["extended-thinking-2025-01-16"]; forwarded to upstream as the anthropic-beta header |
metadata | object | No | Request metadata |
Response (Anthropic format)
Section titled “Response (Anthropic format)”{ "id": "msg_01XFDUDYJgAACzvnptvVoYEL", "type": "message", "role": "assistant", "content": [ { "type": "text", "text": "Hello! How can I help you today?" } ], "model": "claude-sonnet-4-6-20250929", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 10, "output_tokens": 12 }}cURL example
Section titled “cURL example”curl https://api.bve.me/v1/messages \ -H "Authorization: Bearer sk-bve-YOUR_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [ { "role": "user", "content": "Hello, Claude!" } ] }'Streaming
Section titled “Streaming”curl https://api.bve.me/v1/messages \ -H "Authorization: Bearer sk-bve-YOUR_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-sonnet-4-6", "max_tokens": 1024, "messages": [{ "role": "user", "content": "Count to 5." }], "stream": true }'Streaming returns Anthropic SSE events: message_start, content_block_start, content_block_delta, content_block_stop, message_delta, message_stop.
Anthropic SDK
Section titled “Anthropic SDK”BVE Gateway accepts x-api-key as an alternative to Authorization: Bearer, so the standard Anthropic SDK configuration works without modification.
Basic message
Section titled “Basic message”import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({ apiKey: 'sk-bve-YOUR_KEY', baseURL: 'https://api.bve.me',});
const message = await client.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello, Claude!' }],});
console.log(message.content[0].text);import anthropic
client = anthropic.Anthropic( api_key="sk-bve-YOUR_KEY", base_url="https://api.bve.me",)
message = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, messages=[{"role": "user", "content": "Hello, Claude!"}],)
print(message.content[0].text)Streaming
Section titled “Streaming”const stream = await client.messages.stream({ model: 'claude-sonnet-4-6', max_tokens: 1024, messages: [{ role: 'user', content: 'Count to 5.' }],});
for await (const chunk of stream) { if (chunk.type === 'content_block_delta' && chunk.delta.type === 'text_delta') { process.stdout.write(chunk.delta.text); }}with client.messages.stream( model="claude-sonnet-4-6", max_tokens=1024, messages=[{"role": "user", "content": "Count to 5."}],) as stream: for text in stream.text_stream: print(text, end="", flush=True)Available Claude models
Section titled “Available Claude models”| Model | ID |
|---|---|
| Claude Sonnet 4.6 | claude-sonnet-4-6 |
| Claude Sonnet 4.5 | claude-sonnet-4-5 |
| Claude Sonnet 4 | claude-sonnet-4 |
| Claude Haiku 4.5 | claude-haiku-4-5 |
| Claude Haiku 4 | claude-haiku-4 |
| Claude 3.7 Sonnet | claude-3-7-sonnet |
| Claude 3.5 Sonnet | claude-3-5-sonnet |
| Claude 3.5 Haiku | claude-3-5-haiku |
See the Models page for the full list.
Gateway validation
Section titled “Gateway validation”The gateway validates required fields before forwarding to the Anthropic upstream. Note that max_tokens is required by the Anthropic API — unlike /v1/chat/completions which treats it as optional.
Required fields
Section titled “Required fields”| Missing / invalid | Code | param |
|---|---|---|
model absent | missing_required_parameter | "model" |
model not a string | invalid_type | "model" |
max_tokens absent | missing_required_parameter | "max_tokens" |
max_tokens not a number | invalid_type | "max_tokens" |
max_tokens not a positive integer | invalid_value | "max_tokens" |
messages absent | missing_required_parameter | "messages" |
messages not an array | invalid_type | "messages" |
messages is empty | invalid_value | "messages" |
Message-level validation
Section titled “Message-level validation”Each entry in messages[] is validated before forwarding to the upstream. Only user and assistant roles are accepted — unlike /v1/chat/completions, which also accepts system, tool, and developer.
| Condition | Code | param |
|---|---|---|
messages[N] not an object | invalid_type | "messages[N]" |
messages[N].role absent | missing_required_parameter | "messages[N].role" |
messages[N].role not a string | invalid_type | "messages[N].role" |
messages[N].role not user or assistant | invalid_value | "messages[N].role" |
messages[0].role is not user | invalid_value | "messages[0].role" |
| Two consecutive messages share the same role | invalid_value | "messages[N].role" |
messages[N].content not a string or array | invalid_type | "messages[N].content" |
When content is an array (multimodal format), each block is validated — see Content block validation below.
Content block validation
Section titled “Content block validation”The Anthropic Messages API uses a different content block format from the OpenAI image_url format used in /v1/chat/completions. When messages[N].content is an array, the gateway validates each block according to its type field.
All blocks:
| Condition | Code | param |
|---|---|---|
| Block not an object | invalid_type | "messages[N].content[M]" |
type absent | missing_required_parameter | "messages[N].content[M].type" |
type not a string | invalid_type | "messages[N].content[M].type" |
Unknown block types (thinking, redacted_thinking, and any future Anthropic types) pass through without validation.
type: "text" blocks:
| Condition | Code | param |
|---|---|---|
text absent | missing_required_parameter | "messages[N].content[M].text" |
text not a string | invalid_type | "messages[N].content[M].text" |
type: "image" blocks (base64):
{ "type": "image", "source": { "type": "base64", "media_type": "image/jpeg", "data": "/9j/4AAQ..." }}| Condition | Code | param |
|---|---|---|
source absent | missing_required_parameter | "messages[N].content[M].source" |
source not an object | invalid_type | "messages[N].content[M].source" |
source.type absent | missing_required_parameter | "messages[N].content[M].source.type" |
source.type not "base64" or "url" | invalid_value | "messages[N].content[M].source.type" |
source.media_type absent (base64) | missing_required_parameter | "messages[N].content[M].source.media_type" |
source.media_type not one of image/jpeg, image/png, image/gif, image/webp | invalid_value | "messages[N].content[M].source.media_type" |
source.data absent or empty (base64) | missing_required_parameter / invalid_value | "messages[N].content[M].source.data" |
type: "image" blocks (URL):
{ "type": "image", "source": { "type": "url", "url": "https://example.com/photo.jpg" }}| Condition | Code | param |
|---|---|---|
source.url absent or empty (url) | missing_required_parameter / invalid_value | "messages[N].content[M].source.url" |
type: "tool_use" blocks:
{ "type": "tool_use", "id": "toolu_01abc", "name": "get_weather", "input": { "location": "London" }}| Condition | Code | param |
|---|---|---|
id absent or empty | missing_required_parameter / invalid_value | "messages[N].content[M].id" |
name absent or empty | missing_required_parameter / invalid_value | "messages[N].content[M].name" |
input absent | missing_required_parameter | "messages[N].content[M].input" |
input not an object (including arrays) | invalid_type | "messages[N].content[M].input" |
type: "tool_result" blocks:
{ "type": "tool_result", "tool_use_id": "toolu_01abc", "content": "The weather in London is 15°C and cloudy."}| Condition | Code | param |
|---|---|---|
tool_use_id absent or empty | missing_required_parameter / invalid_value | "messages[N].content[M].tool_use_id" |
content not a string or array (when present) | invalid_type | "messages[N].content[M].content" |
type: "document" blocks:
| Condition | Code | param |
|---|---|---|
source absent | missing_required_parameter | "messages[N].content[M].source" |
source not an object | invalid_type | "messages[N].content[M].source" |
source.type absent | missing_required_parameter | "messages[N].content[M].source.type" |
source.type not "base64", "url", or "text" | invalid_value | "messages[N].content[M].source.type" |
source.url absent or empty (url type) | missing_required_parameter / invalid_value | "messages[N].content[M].source.url" |
source.data absent or empty (base64 or text type) | missing_required_parameter / invalid_value | "messages[N].content[M].source.data" |
Anthropic tools validation
Section titled “Anthropic tools validation”Anthropic tools use a different shape from OpenAI function tools. The required field is input_schema (not parameters), and tools are top-level objects without the type: "function" wrapper.
{ "tools": [ { "name": "get_weather", "description": "Get the current weather for a city", "input_schema": { "type": "object", "properties": { "city": { "type": "string" } }, "required": ["city"] } } ]}| Condition | Code | param |
|---|---|---|
tools not an array | invalid_type | "tools" |
tools empty array | invalid_value | "tools" |
tools[N] not an object | invalid_type | "tools[N]" |
tools[N].name absent or empty | missing_required_parameter / invalid_value | "tools[N].name" |
tools[N].name fails [a-zA-Z0-9_-]{1,64} pattern | invalid_value | "tools[N].name" |
tools[N].description not a string (when present) | invalid_type | "tools[N].description" |
tools[N].input_schema absent | missing_required_parameter | "tools[N].input_schema" |
tools[N].input_schema not an object | invalid_type | "tools[N].input_schema" |
Anthropic tool_choice validation
Section titled “Anthropic tool_choice validation”Anthropic tool_choice must be an object — unlike OpenAI, which also accepts bare strings ("none", "auto", "required").
| Condition | Code | param |
|---|---|---|
tool_choice not an object | invalid_type | "tool_choice" |
tool_choice.type absent | missing_required_parameter | "tool_choice.type" |
tool_choice.type not "auto", "any", or "tool" | invalid_value | "tool_choice.type" |
tool_choice.name absent when type is "tool" | missing_required_parameter | "tool_choice.name" |
Valid tool_choice shapes:
{ "type": "auto" }{ "type": "any" }{ "type": "tool", "name": "get_weather" }Optional field constraints
Section titled “Optional field constraints”| Parameter | Invalid condition | Code |
|---|---|---|
temperature | Not a number | invalid_type |
temperature | Outside [0, 1] (note: Anthropic range is [0, 1], not [0, 2]) | invalid_value |
top_p | Not a number | invalid_type |
top_p | Outside [0, 1] | invalid_value |
top_k | Not a positive integer | invalid_value |
stream | Not a boolean | invalid_type |
system | Not a string or array | invalid_type |
stop_sequences | Not an array | invalid_type |
stop_sequences | Array with more than 4 elements | invalid_value |
stop_sequences[N] | Not a string | invalid_type |
metadata | Not an object | invalid_type |
metadata.user_id | Not a string | invalid_type |
thinking | Not an object | invalid_type |
thinking.type | Missing | missing_required_parameter |
thinking.type | Not "enabled" or "disabled" | invalid_value |
thinking.budget_tokens | Missing when thinking.type is "enabled" | missing_required_parameter |
thinking.budget_tokens | Not an integer ≥ 1024 | invalid_value |
All 400 responses use the standard error envelope:
{ "error": { "message": "max_tokens is required", "type": "invalid_request_error", "param": "max_tokens", "code": "missing_required_parameter" }}Response headers
Section titled “Response headers”BVE Gateway adds the following headers to every authenticated response:
| Header | Example | Description |
|---|---|---|
X-Request-Id | 550e8400-… | UUID for this request (generated per request) |
X-BVE-Client-Id | my-trace-123 | Echo of the client-supplied X-Request-Id (when present and valid: alphanumeric + -_., ≤ 128 chars). Absent when not supplied or value failed validation. |
X-BVE-Latency | 143 | Total gateway latency in milliseconds |
X-BVE-Model | claude-sonnet-4 | Model ID resolved for this request |
X-BVE-Key-Name | prod-key | Name of the API key used for this request (redacted if it matches a provider credential pattern) |
The full X-RateLimit-* header set (RPM, RPD, monthly) is also included. See Rate Limits & Quotas for details and example output.
The Anthropic upstream also forwards a request-id response header which BVE Gateway passes through to the client.
- The
anthropic-versionheader is optional but recommended. BVE Gateway forwards it to the upstream provider. - Authentication: BVE Gateway accepts two auth formats on all endpoints:
Authorization: Bearer sk-bve-YOUR_KEY— standard BVE Gateway format (cURL, OpenAI SDK)x-api-key: sk-bve-YOUR_KEY— Anthropic SDK default format (no SDK configuration required)
- Extended thinking (
"thinking": {"type": "enabled", "budget_tokens": N}) is forwarded if the model supports it. - The
betasarray maps directly to theanthropic-betarequest header sent to Anthropic. Pass feature flag strings such as"extended-thinking-2025-01-16"or"interleaved-thinking-2025-05-14"to opt in to beta features. Unknown values are forwarded unchanged and may be silently ignored by Anthropic.