Endpoints

Anthropic Messages API

An Anthropic-compatible /v1/messages endpoint so the Anthropic SDKs and Claude Code work out of the box.

POST /v1/messages

Pendra translates Anthropic's Messages format to OpenAI Chat Completions under the hood, so you can point any Anthropic SDK — or Claude Code — at Pendra and get Anthropic-shaped responses back. The Python and JavaScript examples use the official Anthropic SDKs; only the base URL and key change.

Authentication

Send your Pendra API key on the x-api-key header (Anthropic convention). The Authorization: Bearer header also works for OpenAI-style clients.

Body application/json
model string required
Model ID. Must be a model your workers serve — browse available models. An id Pendra doesn't serve returns 404.
max_tokens integer required
Maximum number of tokens to generate.
messages array required
The conversation — an array of { role, content } objects.
system string | array
System prompt — a string, or an array of content blocks.
stream boolean default: false
Set true for Anthropic-format SSE (see Streaming below).
tools array
Anthropic-shaped tool definitions. Pair with tool_choice.

Other optional fields

  • temperature, top_p, top_k, stop_sequences — standard sampling controls.
  • thinking — pass {"type":"enabled"} to request a chain-of-thought on reasoning-capable models, or {"type":"disabled"} to skip thinking on hybrid models that support toggling it. See Thinking.
  • cache_control — accepted and ignored. You don't need to place cache breakpoints on Pendra; see Prompt caching below.

Response

Pendra returns the Anthropic message envelope (see example). content is an array of content blocks (text, tool_use, etc.). stop_reason is "end_turn" on a natural finish or "max_tokens" when capped. usage uses Anthropic naming (input_tokens / output_tokens).

Prompt caching

Pendra caches prompts automatically. When you send follow-up turns of the same conversation, it reuses the part of the prompt it already processed instead of reprocessing it from scratch, which makes the reply start sooner. You don't have to do anything to switch this on.

usage.cache_read_input_tokens tells you how many of that request's input_tokens came from the cache, so a cache hit rate is cache_read_input_tokens / input_tokens. It's always present — 0 on a first, cold request. A higher rate means a faster first token on that request.

cache_creation_input_tokens is always 0. Other providers charge for writing a prompt into the cache and report it here; Pendra's caching is automatic and free, so there is nothing to report.

Because it's automatic, Pendra doesn't use cache breakpoints. If your client sends cache_control blocks — Claude Code and the Anthropic SDKs do — Pendra accepts the request and ignores the field. You can leave your breakpoints in place; they simply have no effect, and removing them changes nothing. Tuning them won't move your hit rate either: cache_read_input_tokens is the number to watch, and it reflects what Pendra reused on its own.

On a streamed response, the cache numbers arrive on the final message_delta event rather than message_start — they aren't known until the model has finished reading the prompt.

Streaming

Set "stream": true to receive Anthropic-format SSE events (message_start, content_block_delta, message_delta, message_stop).

event: message_start
data: {"type":"message_start","message":{"id":"msg_01N3Xc8","type":"message","role":"assistant","content":[],"model":"qwen3.6:27b","stop_reason":null,"usage":{"input_tokens":9,"output_tokens":0}}}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}

event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"input_tokens":9,"output_tokens":2,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}

event: message_stop
data: {"type":"message_stop"}

While the model is still reading a long prompt, Pendra sends an SSE comment line (: keep-alive) every 15 seconds so the connection isn't dropped as idle before message_start arrives. It carries no event: and no data:, so the Anthropic SDKs and Claude Code discard it; only hand-rolled parsers need to skip lines starting with :.

If you pin a worker with X-Pendra-Worker-Id and it can't serve the request — it doesn't exist, doesn't have the model, or is running a version too old for this endpoint — you get a normal HTTP error response instead of a stream, so check the status code before you start reading events. A worker that's merely busy, or not connected yet, still reports in-band as an error event once the stream has started. See Errors & rate limits.

Pin a Pendra model

Claude Code defaults to Anthropic's own model names, like claude-sonnet-4-5. Pendra serves open models under their own ids and won't substitute one for a name it doesn't recognise, so those requests come back as a 404 telling you which variable to set. Pick an id from /models and set it before you start.

Use with Claude Code

Point Claude Code at Pendra by setting four environment variables — see the full recipe in Integrations → Claude Code.