Create an Anthropic-compatible message
curl --request POST \
--url https://nano-gpt.com/api/v1/messages \
--header 'Content-Type: application/json' \
--data '
{
"model": "<string>",
"messages": [
{
"content": "<string>"
}
],
"max_tokens": 123
}
'import requests
url = "https://nano-gpt.com/api/v1/messages"
payload = {
"model": "<string>",
"messages": [{ "content": "<string>" }],
"max_tokens": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model: '<string>', messages: [{content: '<string>'}], max_tokens: 123})
};
fetch('https://nano-gpt.com/api/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'messages' => [
[
'content' => '<string>'
]
],
'max_tokens' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/messages"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://nano-gpt.com/api/v1/messages")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"type": "<string>",
"role": "<string>",
"model": "<string>",
"content": [
{}
],
"stop_reason": "<string>",
"usage": {}
}{
"error": 123,
"message": "<string>"
}Endpoint Examples
Messages
Accepts Anthropic Messages requests, including video blocks for models that advertise video input.
POST
/
v1
/
messages
Create an Anthropic-compatible message
curl --request POST \
--url https://nano-gpt.com/api/v1/messages \
--header 'Content-Type: application/json' \
--data '
{
"model": "<string>",
"messages": [
{
"content": "<string>"
}
],
"max_tokens": 123
}
'import requests
url = "https://nano-gpt.com/api/v1/messages"
payload = {
"model": "<string>",
"messages": [{ "content": "<string>" }],
"max_tokens": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({model: '<string>', messages: [{content: '<string>'}], max_tokens: 123})
};
fetch('https://nano-gpt.com/api/v1/messages', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/messages",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => '<string>',
'messages' => [
[
'content' => '<string>'
]
],
'max_tokens' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/messages"
payload := strings.NewReader("{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://nano-gpt.com/api/v1/messages")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/messages")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"<string>\",\n \"messages\": [\n {\n \"content\": \"<string>\"\n }\n ],\n \"max_tokens\": 123\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"type": "<string>",
"role": "<string>",
"model": "<string>",
"content": [
{}
],
"stop_reason": "<string>",
"usage": {}
}{
"error": 123,
"message": "<string>"
}To set reasoning effort through a custom model ID, append
:reasoning-effort/high (also low, medium, xhigh, or max). NanoGPT strips the suffix before routing and uses it as the default output_config.effort. Explicit body generation settings take precedence; supported efforts remain model/provider-specific. See Reasoning Effort Suffixes./v1/messages accepts compressed request bodies (Content-Encoding: gzip, deflate, or br) on authenticated JSON requests — useful for long conversations, where compressed uploads cut time-to-first-token. See Compressed Request Bodies.Overview
The/v1/messages endpoint provides full Anthropic API compatibility. Clients using the Anthropic SDK can use NanoGPT by simply changing the base URL — no code changes required.
NanoGPT accepts requests in the Anthropic Messages format, routes them to the requested NanoGPT model, and returns responses back in the Anthropic Messages shape.
For non‑Anthropic models, NanoGPT transparently translates the request to an OpenAI-style chat format internally and then converts the response back to Anthropic Messages format.
This endpoint supports:
- Text generation (streaming and non-streaming)
- Multi-turn conversations
- Tool use (function calling)
- Vision (images), video understanding, and document/PDF processing
- Extended thinking (reasoning models)
- Prompt caching
- Token estimates via
POST /api/v1/messages/count_tokens
Endpoint
POST https://nano-gpt.com/api/v1/messages
Authentication
Use either header:Authorization: Bearer YOUR_API_KEYx-api-key: YOUR_API_KEY
Request Format
Required Fields
| Field | Type | Description |
|---|---|---|
model | string | Model identifier (any NanoGPT model, including non‑Anthropic models) |
max_tokens | number | Maximum tokens to generate (must be a finite number) |
messages | array | Array of conversation messages |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
system | string or array | — | System prompt (string or array of text blocks) |
stream | boolean | false | Enable streaming responses |
temperature | number | — | Sampling temperature |
top_p | number | — | Nucleus sampling parameter |
top_k | number | — | Top-k sampling parameter |
stop_sequences | string[] | — | Custom stop sequences |
tools | array | — | Tool definitions for function calling |
tool_choice | string or object | — | Control tool selection behavior |
disable_parallel_tool_use | boolean | — | Disable parallel tool calls |
thinking | object | — | Enable extended thinking for supported models |
metadata | object | — | Request metadata (user or user_id) |
service_tier | string | — | Service tier: "auto", "default", "standard", "flex", "priority", or "batch" |
Message Format
Messages must have arole (user or assistant) and content:
{
"role": "user",
"content": "Hello!"
}
{
"role": "user",
"content": [
{ "type": "text", "text": "What's in this image?" },
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "<base64-encoded-image>"
}
}
]
}
Content Block Types
Text Block
{ "type": "text", "text": "Your message here" }
Image Block (for vision-capable models)
{
"type": "image",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "<base64-data>"
}
}
{
"type": "image",
"source": {
"type": "url",
"url": "https://example.com/image.jpg"
}
}
image/jpeg, image/png, image/gif, image/webp
Document Block (for PDF-capable models)
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": "<base64-data>"
}
}
Video Block (for video-capable models)
Usetype: "video" for new integrations. Inline video bytes use an Anthropic base64 source:
{
"type": "video",
"source": {
"type": "base64",
"media_type": "video/mp4",
"data": "AAAA..."
}
}
{
"type": "video",
"source": {
"type": "url",
"url": "https://cdn.example.com/clip.mp4",
"media_type": "video/mp4"
}
}
document compatibility block with a video/* source is normalized as video. A document with application/pdf remains a document. Public remote sources must use HTTPS; inline base64 must be valid. See the Video Input guide for model discovery, limits, errors, segment behavior, and the equivalent Chat Completions and Responses shapes.
Tool Use Block (in assistant messages)
{
"type": "tool_use",
"id": "tool_abc123",
"name": "get_weather",
"input": { "city": "Paris" }
}
Tool Result Block (in user messages)
{
"type": "tool_result",
"tool_use_id": "tool_abc123",
"content": "The weather in Paris is sunny, 22 C"
}
Tool Definitions
{
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City name" }
},
"required": ["city"]
}
}
]
}
Tool Choice Options
| Value | Description |
|---|---|
"auto" | Model may use tools if appropriate |
"none" | Disable tool use for this request |
"any" | Model must use at least one tool |
"required" | Model must use at least one tool |
{"type": "tool", "name": "tool_name"} | Force use of a specific tool |
Extended Thinking
For models that support extended thinking (reasoning):{
"thinking": {
"type": "enabled",
"budget_tokens": 8192
}
}
budget_tokensmust be >= 1024budget_tokensmust be <max_tokens- Model must support thinking for the exact model ID you send (check
GET /api/v1/models)
:thinking is model-specific and only works when that exact ID (or a documented alias) exists.
-thinking is a legacy alias pattern for some model families only, not universal.
Do not assume -thinking works for arbitrary model IDs. Always check GET /api/v1/models for exact valid IDs.
If the requested model does not support thinking, NanoGPT automatically ignores/strips the thinking parameter and routes the request to the base model.
For Chat Completions compatibility controls, :reasoning-exclude (or reasoning.exclude) only hides reasoning output; it does not force reasoning compute off. Use reasoning_effort / reasoning.effort to control reasoning depth, and set none to disable reasoning behavior.
Change Effort Within a Conversation
GPT-6 Astra and Claude Fable 5.1 support inline reasoning effort updates using an empty system message:{"role":"system","content":[],"output_config":{"effort":"high"}}. Set the baseline with request-level output_config.effort and leave it unchanged. A trailing update applies to the generated answer; preserve it before assistant output when replaying history. This Messages form requires the system role.
Response Format
Non-Streaming Response
{
"id": "msg_abc123",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-5-20251101",
"content": [
{ "type": "text", "text": "Hello! How can I help you today?" }
],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 10,
"output_tokens": 12,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0,
"cache_creation": {
"ephemeral_5m_input_tokens": 0,
"ephemeral_1h_input_tokens": 0
},
"service_tier": "standard"
}
}
Stop Reasons
| Stop Reason | Description |
|---|---|
end_turn | Natural end of response |
max_tokens | Hit token limit |
stop_sequence | Hit a custom stop sequence |
tool_use | Model wants to use a tool |
content_filter | Content was filtered |
Streaming Response (SSE)
See also: Streaming Protocol (SSE). Whenstream: true, the response is Server-Sent Events with named event types:
Event: message_start
event: message_start
data: {"type": "message_start", "message": {"id": "msg_abc", "type": "message", "role": "assistant", "model": "claude-opus-4-5-20251101", "content": [], "stop_reason": null, "usage": {"input_tokens": 10, "output_tokens": 0}}}
Event: content_block_start
event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}}
Event: content_block_delta
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}}
Event: content_block_stop
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
Event: message_delta
event: message_delta
data: {"type": "message_delta", "delta": {"stop_reason": "end_turn"}, "usage": {"output_tokens": 12}}
Event: message_stop
event: message_stop
data: {"type": "message_stop"}
Streaming Tool Use
When the model uses tools during streaming:event: content_block_start
data: {"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "tool_abc", "name": "get_weather", "input": {}}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"city\":"}}
event: content_block_delta
data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": " \"Paris\"}"}}
event: content_block_stop
data: {"type": "content_block_stop", "index": 0}
Supported Models
Claude Models (Full Support)
| Model | Streaming | Tools | Vision | Thinking |
|---|---|---|---|---|
| claude-opus-4-6 | Yes | Yes | Yes | Yes* |
| claude-opus-4-5-20251101 | Yes | Yes | Yes | Yes* |
| claude-opus-4-1-20250805 | Yes | Yes | Yes | Yes* |
| claude-sonnet-4-5-20250929 | Yes | Yes | Yes | Yes* |
GET /api/v1/models.
Other Models (Via Compatibility Layer)
The v1/messages endpoint also works with non-Anthropic models:| Model | Streaming | Tools | Vision |
|---|---|---|---|
| Yes | Yes | Yes | |
| google/gemini-3-flash-preview | Yes | Yes | Yes |
| google/gemini-3.1-pro-preview | Yes | Yes | Yes |
| zai-org/glm-4.7 | Yes | Yes | — |
Prompt Caching
For the full guide (supported models, thresholds, pricing, and usage fields), see Prompt Caching. NanoGPT automatically applies implicit caching on providers/models that support it (including OpenAI, Gemini, and many open-source provider/model routes), with no extra request flags. Use explicit prompt-caching controls on Claude when you need deterministic cache boundaries, TTL selection, orstickyProvider consistency control.
Enable via Header
anthropic-beta: prompt-caching-2024-07-31
TTL Options
- Default: 5-minute cache TTL
- Extended: Add
extended-cache-ttl-2025-04-11to request 1-hour TTL on Anthropic-native Claude flows
Cache Control in Content (Explicit Claude Controls)
Addcache_control to content blocks for explicit Claude caching:
{
"type": "text",
"text": "This is a long system prompt...",
"cache_control": { "type": "ephemeral" }
}
Cache Usage in Response
{
"usage": {
"input_tokens": 100,
"output_tokens": 50,
"cache_creation_input_tokens": 80,
"cache_read_input_tokens": 0
}
}
Error Handling
For a general guide across NanoGPT APIs, see Error Handling.Error Response Format
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "max_tokens is required",
"param": "max_tokens"
}
}
Error Types
| HTTP Status | Error Type | Description |
|---|---|---|
| 400 | invalid_request_error | Invalid request (missing fields, bad format) |
| 401 | authentication_error | Invalid or missing API key |
| 403 | permission_error | Insufficient permissions |
| 404 | not_found_error | Unknown model |
| 429 | rate_limit_error | Rate limit exceeded |
| 500+ | api_error | Server error |
X-Request-ID header for support requests.
Headers
Request Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes* | Bearer token authentication |
x-api-key | Yes* | Alternative API key header |
Content-Type | Yes | Must be application/json |
anthropic-beta | No | Enable beta features (e.g., prompt caching) |
anthropic-version | No | API version (accepted but not required) |
Authorization or x-api-key is required.
BYOK Headers
For Bring Your Own Key:| Header | Description |
|---|---|
x-use-byok | Set to true to use your own API key |
x-byok-provider | Provider name for your key |
Examples
Basic Request (cURL)
curl -X POST https://nano-gpt.com/api/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "claude-opus-4-5-20251101",
"max_tokens": 256,
"messages": [
{ "role": "user", "content": "Hello!" }
]
}'
Streaming Request (cURL)
curl -N -X POST https://nano-gpt.com/api/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "claude-opus-4-5-20251101",
"stream": true,
"max_tokens": 256,
"messages": [{ "role": "user", "content": "Hello!" }]
}'
With Tools (cURL)
curl -X POST https://nano-gpt.com/api/v1/messages \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{
"model": "claude-opus-4-5-20251101",
"max_tokens": 1024,
"tools": [
{
"name": "get_weather",
"description": "Get current weather",
"input_schema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
],
"messages": [
{ "role": "user", "content": "What is the weather in Tokyo?" }
]
}'
Anthropic SDK (Node.js)
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.NANOGPT_API_KEY,
baseURL: "https://nano-gpt.com/api"
});
const message = await anthropic.messages.create({
model: "claude-opus-4-5-20251101",
max_tokens: 256,
messages: [
{ role: "user", content: "Hello!" }
]
});
console.log(message.content[0].text);
Anthropic SDK with Streaming (Node.js)
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.NANOGPT_API_KEY,
baseURL: "https://nano-gpt.com/api"
});
const stream = await anthropic.messages.stream({
model: "claude-opus-4-5-20251101",
max_tokens: 256,
messages: [
{ role: "user", content: "Tell me a story" }
]
});
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
Anthropic SDK with Prompt Caching (Node.js)
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({
apiKey: process.env.NANOGPT_API_KEY,
baseURL: "https://nano-gpt.com/api"
});
const message = await anthropic.messages.create({
model: "claude-opus-4-5-20251101",
max_tokens: 256,
system: [
{
type: "text",
text: "You are a helpful assistant with expertise in...",
cache_control: { type: "ephemeral" }
}
],
messages: [
{ role: "user", content: "Hello!" }
]
}, {
headers: {
"anthropic-beta": "prompt-caching-2024-07-31"
}
});
Vision Example (Node.js)
import Anthropic from "@anthropic-ai/sdk";
import fs from "fs";
const anthropic = new Anthropic({
apiKey: process.env.NANOGPT_API_KEY,
baseURL: "https://nano-gpt.com/api"
});
const imageData = fs.readFileSync("image.jpg").toString("base64");
const message = await anthropic.messages.create({
model: "claude-opus-4-5-20251101",
max_tokens: 1024,
messages: [
{
role: "user",
content: [
{ type: "text", text: "What's in this image?" },
{
type: "image",
source: {
type: "base64",
media_type: "image/jpeg",
data: imageData
}
}
]
}
]
});
Python SDK
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_NANOGPT_API_KEY",
base_url="https://nano-gpt.com/api"
)
message = client.messages.create(
model="claude-opus-4-5-20251101",
max_tokens=256,
messages=[
{"role": "user", "content": "Hello!"}
]
)
print(message.content[0].text)
Limits
| Limit | Value |
|---|---|
| Request timeout | 800 seconds |
| Tool argument size | ~100 KB per tool call |
| Image types | JPEG, PNG, GIF, WebP |
Limitations
- GPU-TEE models do not support streaming through
POST /api/v1/messages. UsePOST /api/v1/chat/completionsif you need streaming with GPU-TEE models.
Migration from Anthropic
To migrate from Anthropic’s API to NanoGPT:-
Change the base URL:
- From:
https://api.anthropic.com - To:
https://nano-gpt.com/api
https://nano-gpt.com/api/v1/messages - From:
- Use your NanoGPT API key instead of your Anthropic key
- No other code changes required — the API is fully compatible
Service tier compatibility
Anthropic-style service tiers are normalized when routing to providers that support service tiers:standard→defaultdefault→defaultflex→flexpriority→prioritybatch→ ignored for service-tier routing
Notes
- The
anthropic-versionheader is accepted but not required - Token usage numbers use NanoGPT’s token accounting (may differ slightly from Anthropic’s exact counts)
- All Anthropic SDK features are supported, including streaming, tools, and caching
Body
application/json
Model ID or compatibility alias. Append :reasoning-effort/, where effort is low, medium, high, xhigh, or max, to supply a default output_config.effort. The suffix is stripped before model routing and is not listed as a separate model. Explicit reasoning generation settings take precedence. Effort support remains model/provider-specific.
Show child attributes
Show child attributes
Request-level effort baseline. Keep unchanged when using inline output_config updates.
Show child attributes
Show child attributes