- Supports multi-turn conversations, streaming SSE, tool use, and extended thinking
- Supports multimodal content including text and images
- Responses are upstream passthrough with no
{code, data}wrapper
Do not mix the two APIs: /v1/* is the inference API (this document — upstream passthrough, no wrapper); /api/* is the management API (balance/logs, etc., response shape {success, message, data}). If you see docs claiming /v1/messages returns {code, data}, this document takes precedence.
Request example
curl https://api.openveer.com/v1/messages \
-H "x-api-key: $API_KEY" \
-H "anthropic-version: 2025-10-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Hello, world"}
]
}'import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.openveer.com"
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "Hello, world"}
]
)
print(message.content)import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.API_KEY,
baseURL: 'https://api.openveer.com'
});
const message = await client.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Hello, world' }
]
});
console.log(message.content);package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
url := "https://api.openveer.com/v1/messages"
payload := map[string]interface{}{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": []map[string]string{
{
"role": "user",
"content": "Hello, world",
},
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("x-api-key", os.Getenv("API_KEY"))
req.Header.Set("anthropic-version", "2025-10-01")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URI;
public class Main {
public static void main(String[] args) throws Exception {
String url = "https://api.openveer.com/v1/messages";
String apiKey = System.getenv("API_KEY");
String payload = """
{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Hello, world"
}
]
}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("x-api-key", apiKey)
.header("anthropic-version", "2025-10-01")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}<?php
$url = "https://api.openveer.com/v1/messages";
$apiKey = getenv('API_KEY');
$payload = [
"model" => "claude-sonnet-4-6",
"max_tokens" => 1024,
"messages" => [
[
"role" => "user",
"content" => "Hello, world"
]
]
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"x-api-key: " . $apiKey,
"anthropic-version: 2025-10-01",
"Content-Type: application/json"
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>require 'net/http'
require 'json'
require 'uri'
url = URI("https://api.openveer.com/v1/messages")
api_key = ENV['API_KEY']
payload = {
model: "claude-sonnet-4-6",
max_tokens: 1024,
messages: [
{
role: "user",
content: "Hello, world"
}
]
}
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = api_key
request["anthropic-version"] = "2025-10-01"
request["Content-Type"] = "application/json"
request.body = payload.to_json
response = http.request(request)
puts response.bodyimport Foundation
let url = URL(string: "https://api.openveer.com/v1/messages")!
let apiKey = ProcessInfo.processInfo.environment["API_KEY"] ?? ""
let payload: [String: Any] = [
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
[
"role": "user",
"content": "Hello, world"
]
]
]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
request.setValue("2025-10-01", forHTTPHeaderField: "anthropic-version")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONSerialization.data(withJSONObject: payload)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
return
}
if let data = data, let responseString = String(data: data, encoding: .utf8) {
print(responseString)
}
}
task.resume()using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var url = "https://api.openveer.com/v1/messages";
var apiKey = Environment.GetEnvironmentVariable("API_KEY");
var payload = @"{
""model"": ""claude-sonnet-4-6"",
""max_tokens"": 1024,
""messages"": [
{
""role"": ""user"",
""content"": ""Hello, world""
}
]
}";
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("x-api-key", apiKey);
client.DefaultRequestHeaders.Add("anthropic-version", "2025-10-01");
var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
}#include <stdio.h>
#include <curl/curl.h>
#include <stdlib.h>
int main(void) {
CURL *curl;
CURLcode res;
const char *api_key = getenv("API_KEY");
curl_global_init(CURL_GLOBAL_DEFAULT);
curl = curl_easy_init();
if(curl) {
const char *url = "https://api.openveer.com/v1/messages";
const char *payload = "{"
"\"model\":\"claude-sonnet-4-6\","
"\"max_tokens\":1024,"
"\"messages\":[{\"role\":\"user\",\"content\":\"Hello, world\"}]"
"}";
char auth_header[256];
snprintf(auth_header, sizeof(auth_header), "x-api-key: %s", api_key);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, auth_header);
headers = curl_slist_append(headers, "anthropic-version: 2025-10-01");
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
res = curl_easy_perform(curl);
if(res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n",
curl_easy_strerror(res));
}
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
}
curl_global_cleanup();
return 0;
}#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSURL *url = [NSURL URLWithString:@"https://api.openveer.com/v1/messages"];
NSString *apiKey = [NSProcessInfo processInfo].environment[@"API_KEY"];
NSDictionary *payload = @{
@"model": @"claude-sonnet-4-6",
@"max_tokens": @1024,
@"messages": @[
@{
@"role": @"user",
@"content": @"Hello, world"
}
]
};
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:payload
options:0
error:&error];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setValue:apiKey forHTTPHeaderField:@"x-api-key"];
[request setValue:@"2025-10-01" forHTTPHeaderField:@"anthropic-version"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:jsonData];
NSURLSessionDataTask *task = [[NSURLSession sharedSession]
dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
return;
}
NSString *result = [[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding];
NSLog(@"%@", result);
}];
[task resume];
[[NSRunLoop mainRunLoop] run];
}
return 0;
}(* Requires cohttp and yojson libraries *)
open Lwt
open Cohttp
open Cohttp_lwt_unix
let url = "https://api.openveer.com/v1/messages"
let api_key = Sys.getenv "API_KEY"
let payload = {|{
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"messages": [
{
"role": "user",
"content": "Hello, world"
}
]
}|}
let () =
let headers = Header.init ()
|> fun h -> Header.add h "x-api-key" api_key
|> fun h -> Header.add h "anthropic-version" "2025-10-01"
|> fun h -> Header.add h "Content-Type" "application/json"
in
let body = Cohttp_lwt.Body.of_string payload in
let response = Client.post ~headers ~body (Uri.of_string url) >>= fun (resp, body) ->
body |> Cohttp_lwt.Body.to_string >|= fun body_str ->
print_endline body_str
in
Lwt_main.run responseimport 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
void main() async {
final url = Uri.parse('https://api.openveer.com/v1/messages');
final apiKey = Platform.environment['API_KEY'];
final payload = {
'model': 'claude-sonnet-4-6',
'max_tokens': 1024,
'messages': [
{
'role': 'user',
'content': 'Hello, world'
}
]
};
final response = await http.post(
url,
headers: {
'x-api-key': apiKey!,
'anthropic-version': '2025-10-01',
'Content-Type': 'application/json',
},
body: jsonEncode(payload),
);
print(response.body);
}library(httr)
library(jsonlite)
url <- "https://api.openveer.com/v1/messages"
api_key <- Sys.getenv("API_KEY")
payload <- list(
model = "claude-sonnet-4-6",
max_tokens = 1024,
messages = list(
list(
role = "user",
content = "Hello, world"
)
)
)
response <- POST(
url,
add_headers(
`x-api-key` = api_key,
`anthropic-version` = "2025-10-01",
`Content-Type` = "application/json"
),
body = toJSON(payload, auto_unbox = TRUE),
encode = "raw"
)
cat(content(response, "text"))Response example
{
"model": "claude-sonnet-4-6",
"id": "msg_011CdfeHuC728oxqaLrRNbcB",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "Hello! I'm Claude. Nice to meet you."
}
],
"stop_reason": "end_turn",
"stop_sequence": null,
"stop_details": null,
"usage": {
"input_tokens": 12,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"cache_creation": {
"ephemeral_5m_input_tokens": 0,
"ephemeral_1h_input_tokens": 0
},
"output_tokens": 18,
"service_tier": "standard",
"inference_geo": "global"
}
}{
"error": {
"code": "model_not_found",
"message": "model not found (request id: 20260803181903172761862QzohKPXF)",
"param": "",
"type": "openveer_error"
}
}{
"error": {
"code": "",
"message": "Invalid API key (request id: 20260803181903172761862QzohKPXF)",
"param": "",
"type": "openveer_error"
}
}{
"error": {
"code": "",
"message": "Insufficient balance (request id: 20260803181903172761862QzohKPXF)",
"param": "",
"type": "openveer_error"
}
}{
"error": {
"code": "",
"message": "Too many requests (request id: 20260803181903172761862QzohKPXF)",
"param": "",
"type": "openveer_error"
}
}{
"error": {
"code": "",
"message": "Internal server error (request id: 20260803181903172761862QzohKPXF)",
"param": "",
"type": "openveer_error"
}
}Authorizations
Authentication supports two methods — use either one:
x-api-key stringAnthropic-style API key header
Visit the API Key Management Page to get your API Key
x-api-key: YOUR_API_KEYAuthorization stringBearer token authentication (alternative to x-api-key)
Authorization: Bearer YOUR_API_KEYanthropic-version stringAPI version (optional — requests work without it)
Recommended for easier future migration to Anthropic's official endpoint:
Example: 2025-10-01
Body
model stringModel name
claude-opus-4-8- Claude Opus 4.8 flagship modelclaude-opus-4-7- Claude Opus 4.7 flagship modelclaude-opus-4-6- Claude Opus 4.6 flagship modelclaude-sonnet-4-6- Claude Sonnet 4.6 balanced versionclaude-opus-4-5-20251101- Claude Opus 4.5 model
messages arrayrole and content fields.
**💡 Quick fill (Try it area):**
1. Click "+ Add an item" to add a message
2. role input: user (user message) or assistant (AI response, for multi-turn)
3. content input: your message text
role stringRole type
Options: user (user message), assistant (AI response, for multi-turn conversations and prefilling)
Note: Claude API uses a separate system parameter for system prompts, not in messages
content stringMessage content
Text content of the message
[{"role": "user", "content": "Hello, Claude"}][
{"role": "user", "content": "Hello there."},
{"role": "assistant", "content": "Hi, I'm Claude. How can I help you?"},
{"role": "user", "content": "Can you explain LLMs in plain English?"}
][
{"role": "user", "content": "What's the Greek name for Sun? (A) Sol (B) Helios (C) Sun"},
{"role": "assistant", "content": "The best answer is ("}
]max_tokens integerMaximum tokens to generate (required, same as Anthropic official)
Maximum number of tokens to generate before stopping. The model may stop before reaching this limit.
Different models have different maximum values. See model docs. Minimum: 1
thinking objectExtended thinking configuration
When enabled, the response content may include thinking blocks. Prefer the standard model name plus this parameter over platform-side -thinking model aliases, so you can migrate to the official endpoint without code changes.
If multi-turn conversations need to pass thinking blocks back, you must return the signature unchanged, or the upstream will reject the request.
system string | arraySystem prompt
System prompts set Claude's role, personality, goals, and instructions.
String format:
{
"system": "You are a professional Python programming tutor"
}Structured format:
{
"system": [
{
"type": "text",
"text": "You are a professional Python programming tutor"
}
]
}temperature numberTemperature parameter, range 0-1
Controls randomness of output:
- Low values (e.g., 0.2): More deterministic, conservative
- High values (e.g., 0.8): More random, creative
Default: 1.0
top_p numberNucleus sampling parameter, range 0-1
Uses nucleus sampling. Recommend using either temperature or top_p, not both.
Default: 1.0
top_k integerTop-K sampling
Sample from top K options only, removes "long tail" low probability responses.
Recommended for advanced use cases only.
stream booleanEnable streaming
When true, uses Server-Sent Events (SSE) to stream responses.
Default: false
stop_sequences arrayStop sequences
Custom text sequences that cause the model to stop generating.
Maximum 4 sequences.
Example: ["\n\nHuman:", "\n\nAssistant:"]
metadata objectMetadata
Metadata object for the request.
Includes:
user_id: User identifier
tools arrayTool definitions
List of tools the model can use to complete tasks.
Function tool example:
{
"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"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
]
}Supported tool types:
- Custom function tools
- Computer use tool (computer_20241022)
- Text editor tool (text_editor_20241022)
- Bash tool (bash_20241022)
tool_choice objectTool choice strategy
Controls how the model uses tools:
{"type": "auto"}: Auto-decide (default){"type": "any"}: Must use a tool{"type": "tool", "name": "tool_name"}: Use specific tool
Response
id stringUnique message identifier
Example: "msg_013Zva2CMHLNnXjNJJKqJ2EF"
type stringObject type
Always "message"
role stringRole
Always "assistant"
content arrayContent blocks array
content is an array of blocks distinguished by type. A single response may contain multiple blocks (for example, with thinking enabled: a thinking block plus a text block).
text block:
{ "type": "text", "text": "OK" }tool_use block:
{
"type": "tool_use",
"id": "toolu_01QgsazxKXSfQVj9Q1XxjYXo",
"name": "get_weather",
"input": { "city": "Beijing" },
"caller": { "type": "direct" }
}
caller is a newer upstream field not yet documented officially; ignore it when parsing.
thinking block (present when the request body includes the thinking parameter):
{
"type": "thinking",
"thinking": "Reasoning process text...",
"signature": "<signature string, typically 500+ characters>"
}
When returning thinking blocks in multi-turn conversations, you must pass signature back unchanged, or the upstream will reject the request.
Do not assume content[0] is text. With thinking enabled, content[0] may be a thinking block. Iterate and filter:
text = "".join(b.text for b in resp.content if b.type == "text")model stringModel that handled the request
Example: "claude-sonnet-4-6"
stop_reason stringStop reason
Possible values:
end_turn: Natural completionmax_tokens: Reached maximum tokensstop_sequence: Hit stop sequencetool_use: Invoked a tool
stop_sequence string | nullStop sequence triggered
The stop sequence that was generated, if any; otherwise null
stop_details object | nullNewer Anthropic field; null for typical requests
usage objectinput_tokens integerNumber of input tokens
output_tokens integerNumber of output tokens (already includes thinking tokens; do not double-count for billing)
cache_creation_input_tokens integerCache write tokens
cache_read_input_tokens integerCache hit tokens
cache_creation object{ ephemeral_5m_input_tokens, ephemeral_1h_input_tokens }
service_tier stringe.g. "standard"
inference_geo stringe.g. "global"
output_tokens_details objectMay appear when thinking is enabled: { thinking_tokens: int }
Usage Examples
Basic Conversation
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.openveer.com"
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "Explain quantum computing basics"}
]
)
print(message.content[0].text)Multi-turn Conversation
messages = [
{"role": "user", "content": "What is machine learning?"},
{"role": "assistant", "content": "Machine learning is a branch of AI..."},
{"role": "user", "content": "Can you give a practical example?"}
]
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages
)Using System Prompts
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system="You are a senior Python developer expert in code review and optimization.",
messages=[
{"role": "user", "content": "How to optimize this code?\n\n[code]"}
]
)Streaming Response
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "Write a short essay about AI"}
]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)Tool Use
tools = [
{
"name": "get_stock_price",
"description": "Get real-time stock price",
"input_schema": {
"type": "object",
"properties": {
"ticker": {
"type": "string",
"description": "Stock ticker symbol, e.g., AAPL"
}
},
"required": ["ticker"]
}
}
]
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[
{"role": "user", "content": "What's Tesla's stock price?"}
]
)
# Handle tool calls
if message.stop_reason == "tool_use":
tool_use = next(block for block in message.content if block.type == "tool_use")
print(f"Calling tool: {tool_use.name}")
print(f"Arguments: {tool_use.input}")Vision Understanding
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.jpg"
}
},
{
"type": "text",
"text": "Describe this image"
}
]
}
]
)Base64 Image
import base64
with open("image.jpg", "rb") as image_file:
image_data = base64.b64encode(image_file.read()).decode("utf-8")
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{
"role": "user",
"content": [
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": image_data
}
},
{
"type": "text",
"text": "Analyze this image"
}
]
}
]
)Best Practices
1. Prompt Engineering
Clear role definition:
system = """You are an experienced data scientist specializing in:
- Statistical analysis and data visualization
- Machine learning model development
- Python and R programming
Provide professional, accurate advice."""Structured output:
message = "Please return the analysis results in JSON format with summary, key_findings, and recommendations fields."2. Error Handling
from anthropic import APIError, RateLimitError
try:
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}]
)
except RateLimitError:
print("Rate limit exceeded, please retry later")
except APIError as e:
print(f"API error: {e}")3. Token Optimization
# Use shorter prompts
messages = [
{"role": "user", "content": "Summarize key points:\n\n[long text]"}
]
# Limit output length
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500, # Limit output
messages=messages
)4. Prefilling Responses
# Guide model to specific format
messages = [
{"role": "user", "content": "List 5 Python best practices"},
{"role": "assistant", "content": "Here are 5 Python best practices:\n\n1."}
]
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages
)Streaming Response Handling
Python Streaming
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.openveer.com"
)
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[
{"role": "user", "content": "Write a Python decorator example"}
]
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)JavaScript Streaming
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic({
apiKey: process.env.API_KEY,
baseURL: 'https://api.openveer.com'
});
const stream = await client.messages.stream({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
messages: [
{ role: 'user', content: 'Write a React component example' }
]
});
for await (const chunk of stream) {
if (chunk.type === 'content_block_delta' &&
chunk.delta.type === 'text_delta') {
process.stdout.write(chunk.delta.text);
}
}Platform Differences & Integration Notes
Unwrapped response
Successful POST /v1/messages responses return the Anthropic message object directly, with no {code, data} wrapper. This is required for 1:1 compatibility with official SDKs, Claude Code, Cline, and similar tools.
Error format (only real incompatibility with official)
{
"error": {
"code": "model_not_found",
"message": "... (request id: ...)",
"param": "",
"type": "openveer_error"
}
}Compared with Anthropic official: missing top-level "type": "error"; error.type is always openveer_error, not semantic types like invalid_request_error.
Integration guidance: do not branch retries on error.type; use HTTP status + error.code instead:
| Status | Meaning | Suggested action |
|---|---|---|
| 400 | Bad request parameters | Do not retry; fix the request body |
| 401 | Invalid key | Do not retry |
| 402 | Insufficient balance | Do not retry; prompt top-up |
| 429 | Rate limited | Retry with backoff |
| 5xx | Upstream/gateway error | Retry with exponential backoff |
When reporting issues, include the request id at the end of error.message and the response header x-oneapi-request-id.
Streaming SSE
Send "stream": true. Event sequence matches official:
message_start → content_block_start → ping → content_block_delta (multiple) → content_block_stop → message_delta → message_stop
⚠️ Stream vs non-stream usage differs: message_delta.usage typically has only 4 token fields and does not include cache_creation, service_tier, or inference_geo. Parse them separately or treat all as optional.
Unimplemented endpoint
POST /v1/messages/count_tokens is not implemented and returns 404. Official SDK client.messages.count_tokens() will fail. Estimate tokens locally, or read usage.input_tokens from responses.
Ignore unknown fields
This endpoint passes through upstream fields. Anthropic may add fields at any time (e.g. stop_details, inference_geo, caller, output_tokens_details). Do not use strict schemas:
- Go: do not use
DisallowUnknownFields() - Pydantic: do not use
extra="forbid" - TypeScript / Zod: use
.passthrough()instead of.strict()
Model name recommendation
Same-name models with a -thinking suffix are platform extension aliases. Prefer the standard model name without the suffix plus the request-body thinking parameter for easier migration to the official endpoint.
Other request-body fields match official: model, messages, max_tokens (required), system, temperature, top_p, top_k, stop_sequences, stream, tools, tool_choice, thinking, metadata. Semantics follow the Anthropic Messages API.
Important Notes
-
API Key Security:
* Store API keys in environment variables
* Never hardcode keys in source code
* Rotate keys regularly -
Rate Limiting:
* Be aware of API rate limits
* Implement retry mechanisms (by HTTP status code)
* Use exponential backoff -
Token Management:
* Monitor token usage (readusage)
* Optimize prompt length
* Use appropriatemax_tokensvalues
* With thinking enabled,output_tokensalready includes thinking tokens — do not double-count for billing -
Model Selection:
* Opus: Complex tasks, deep thinking required
* Sonnet: Balanced performance and cost
* Haiku: Fast response, simple tasks -
Content parsing:
* Iteratecontentfortype == "text"; do not hardcodecontent[0].text
* If the model returns JSON wrapped in Markdown code fences, that is model output — not an API wrapper (see FAQ below) -
Content Filtering:
* Validate user input
* Filter sensitive information
* Implement content moderation
FAQ
The response content text is a ```json ... ``` code fence — how do I strip it?
This is not an API structure issue. The text field holds the raw model-generated content: if the model decides you want JSON, it may wrap it in a Markdown code fence. The API does not and should not rewrite model output.
To get clean structured data, use one of these three approaches (recommended from highest to lowest reliability):
- Use tools to force structured output — most reliable; the
inputfield is already a parsed object:
{
"tools": [{
"name": "emit_result",
"input_schema": {
"type": "object",
"properties": { "answer": { "type": "string" } }
}
}],
"tool_choice": { "type": "tool", "name": "emit_result" }
}- Prefill the assistant message so the model continues from
{:
{
"messages": [
{ "role": "user", "content": "..." },
{ "role": "assistant", "content": "{" }
]
}- In the system prompt, explicitly require “output JSON only, with no Markdown code fences.”
Do not rely on regex to strip code fences — parsing will break when the model occasionally omits the fence.
This documentation is built and hosted on Mintlify, a developer documentation platform.