Messages
Use the Anthropic SDK, Claude Agent SDK, and Claude Code with Ultramarine.
POST https://api.paperinstruments.com/v1/messages
x-api-key: $PAPERINSTRUMENTS_API_KEY
anthropic-version: 2023-06-01
Content-Type: application/jsonThe endpoint accepts the
Anthropic Messages format
with the parameters listed below. Authenticate with x-api-key or
Authorization: Bearer. If you send both, they must match.
Anthropic SDK
Set the base URL to https://api.paperinstruments.com.
import os
import anthropic
client = anthropic.Anthropic(
base_url="https://api.paperinstruments.com",
api_key=os.environ["PAPERINSTRUMENTS_API_KEY"],
)
with client.messages.stream(
model="ultramarine-1-flash",
max_tokens=8192,
messages=[{"role": "user", "content": "Explain EBITDA."}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)Claude Agent SDK and Claude Code
Set these environment variables:
| Variable | Value |
|---|---|
ANTHROPIC_BASE_URL | https://api.paperinstruments.com |
ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN | Your API key |
ANTHROPIC_MODEL, ANTHROPIC_DEFAULT_OPUS_MODEL, ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL, CLAUDE_CODE_SUBAGENT_MODEL | ultramarine-1-flash |
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS | 1 |
These settings return 400:
| Setting | Use instead |
|---|---|
CLAUDE_CODE_EFFORT_LEVEL=medium or xhigh | low, high, or max |
MAX_THINKING_TOKENS | Leave unset |
CLAUDE_CODE_MAX_OUTPUT_TOKENS above 32768 | 32768 or less |
| Tool search | ENABLE_TOOL_SEARCH=false |
| A one-hour prompt-cache TTL | The default cache setting |
Any model name other than ultramarine-1-flash returns 404.
In Claude Code, WebSearch does not work and reading a PDF returns an error.
import asyncio
import os
from claude_agent_sdk import ClaudeAgentOptions, query
ENV = {
"ANTHROPIC_BASE_URL": "https://api.paperinstruments.com",
"ANTHROPIC_API_KEY": os.environ["PAPERINSTRUMENTS_API_KEY"],
"ANTHROPIC_MODEL": "ultramarine-1-flash",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "ultramarine-1-flash",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "ultramarine-1-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "ultramarine-1-flash",
"CLAUDE_CODE_SUBAGENT_MODEL": "ultramarine-1-flash",
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS": "1",
}
async def main():
options = ClaudeAgentOptions(
model="ultramarine-1-flash", env=ENV, allowed_tools=["Read", "Grep"]
)
async for message in query(prompt="Summarize README.md.", options=options):
print(message)
asyncio.run(main())For the Claude Code CLI, export the same variables and run
claude --model ultramarine-1-flash.
Parameters
| Parameter | Accepted values |
|---|---|
model | Required. ultramarine-1-flash |
max_tokens | Required. 1 to 32,768 |
messages | Required. user and assistant turns, ending with a user turn |
system | String or text blocks |
output_config.effort | low, high, or max. Default: max, which we recommend for best results. |
output_config.format | {"type": "json_schema", "schema": {...}} |
thinking | {"type": "adaptive"}, optionally with "display": "omitted" |
stream | true or false |
tools | Custom tools with name, description, input_schema, and optional strict |
tool_choice | auto, any, none, or {"type": "tool", "name": "..."}, with optional disable_parallel_tool_use |
temperature | 0 to 1 |
top_p | 0 to 1 |
top_k | Integer, 0 or more |
metadata.user_id | String, up to 512 characters |
cache_control | {"type": "ephemeral"} |
context_management | {"edits": [{"type": "clear_thinking_20251015", "keep": "all"}]} |
Tool names use 1 to 64 letters, digits, _, or -. Request bodies can be up
to 20 MiB.
Content
| Block | Where |
|---|---|
text | Any turn |
image with a base64 source (PNG, JPEG, GIF, WebP) or a public url source | User turns and tool_result |
tool_use | Assistant turns |
tool_result with a string, text blocks, or images, and optional is_error | The user turn after the tool_use |
A request can include up to 16 images. Each image can be up to 10 MiB and 32 million pixels, and cannot be animated.
Return one tool_result for each tool_use, before any other content in that
user turn.
To add a system message mid-conversation, send
anthropic-beta: mid-conversation-system-2026-04-07 and include a
{"role": "system"} text turn after a user turn.
Headers
| Header | Accepted values |
|---|---|
anthropic-version | 2023-06-01 |
x-session-id | Optional. A stable conversation ID you can use for sticky routing. Reuse the same value across requests in a conversation. |
anthropic-beta | claude-code-20250219, mid-conversation-system-2026-04-07, effort-2025-11-24, interleaved-thinking-2025-05-14, context-management-2025-06-27 |
Conversation caching
The x-session-id header is optional. You can use it if you want sticky
routing. When using it, reuse the same value for every turn, retry, and tool
result in a conversation, and use a different value for each conversation.
Pass extra_headers={"x-session-id": "your-conversation-id"} in the Python
Anthropic SDK, or { headers: { "x-session-id": "your-conversation-id" } } as
the second argument to messages.create or messages.stream in Node.js.
Keep repeated prompt prefixes stable. Cache hits depend on matching prefixes and cache availability. Send the full message history on each request.
Not supported
Stop sequences (stop_sequences) are not currently supported.
These return 400 invalid_request_error. The error message names the field.
| Feature | Field |
|---|---|
| Disabled thinking and thinking budgets | thinking |
| Summarized thinking | thinking.display |
thinking and redacted_thinking blocks in history | messages |
Effort other than low, high, or max | output_config.effort |
| PDFs and other documents | document blocks |
| Search results | search_result blocks |
| Files API sources | source.type: "file" |
| Citations | citations |
Cache TTLs and cache types other than ephemeral | cache_control |
| Server tools: web search, web fetch, code execution, MCP connector, tool search | tools[].type |
| Deferred tools, programmatic tool calling, toolsets | defer_loading, caller, toolset_name |
| Other context edits and compaction | context_management, compaction |
| Turn-scoped system messages and per-message effort | clear_at, output_config on a message |
| Safeguards | safeguards |
| Assistant prefill | A final assistant turn |
| Images in assistant or system turns | image blocks |
| Other beta headers | anthropic-beta |
| Other API versions | anthropic-version |
Any other field, such as service_tier or inference_geo | The field name |
Token counting, Message Batches, the Files API, and the Models API are not available.
Errors
Errors use the Anthropic error format and include a request_id:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "thinking.type: Disabling thinking is not supported."
},
"request_id": "req_..."
}| Status | Type | When |
|---|---|---|
400 | invalid_request_error | Invalid or unsupported request |
401 | authentication_error | Missing or invalid API key |
404 | not_found_error | Unknown model |
429 | rate_limit_error | Rate limit reached or insufficient balance |
502 | api_error | Invalid model output |
503 | api_error | No capacity. Retry with backoff |
See Errors for retries.