Messages API
PickLayer exposes an Anthropic Messages-compatible surface at:
POST /v1/messagesPOST /v1/messages/count_tokens
Claude Code Setup
Use your app-scoped base URL and Bearer token:
export ANTHROPIC_BASE_URL="https://<picklayer-host>/api/org/<orgHandler>/app/<appHandler>"
export ANTHROPIC_AUTH_TOKEN="Bearer <PL-JWT>"Model values must use PickLayer provider-prefixed IDs, for example:
openrouter/z-ai/glm-4.7-flashopenai/gpt-5.2gemini/gemini-2.5-flashxai/grok-4anthropic/claude-sonnet-4-5-20250929
PickLayer-specific parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
service_tier | string | — | Priority tier. 'auto' = fast (Priority capacity — note: opposite of chat-completions/responses meaning), 'standard_only' = standard default. See Service Tier. |
Basic Request
{
"model": "openrouter/z-ai/glm-4.7-flash",
"max_tokens": 1024,
"system": "You are a helpful coding assistant.",
"messages": [
{ "role": "user", "content": "Explain async/await in JavaScript" }
]
}Response (Bare Anthropic Shape)
{
"id": "msg_xxx",
"type": "message",
"role": "assistant",
"content": [
{ "type": "text", "text": "Async/await is..." }
],
"model": "openrouter/z-ai/glm-4.7-flash",
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 25,
"output_tokens": 312
}
}Streaming
Set "stream": true to receive Anthropic SSE events.
Event Lifecycle
The full event sequence for a successful stream is:
message_start → ping → (content_block_start → content_block_delta* → content_block_stop)* → message_delta → message_stopOn errors the stream ends with error → message_stop.
Full Event Reference
message_start — emitted once at the beginning with the initial message shell:
event: message_start
data: {"type":"message_start","message":{"id":"msg_xxx","type":"message","role":"assistant","content":[],"model":"openrouter/z-ai/glm-4.7-flash","stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":25,"output_tokens":0}}}ping — keepalive, emitted after message_start and periodically during long streams:
event: ping
data: {"type":"ping"}content_block_start — emitted once per content block (text, tool_use, thinking) before its deltas:
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}content_block_delta — one or more per block, carries the incremental content:
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}content_block_stop — emitted once after all deltas for a block are sent:
event: content_block_stop
data: {"type":"content_block_stop","index":0}message_delta — emitted once after all content blocks, carries final stop reason and output token usage:
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":312}}delta.stop_reason values: "end_turn", "max_tokens", "stop_sequence", "tool_use".
usage fields in message_delta:
output_tokens— always presentcache_creation_input_tokens— present when greater than 0cache_read_input_tokens— present when greater than 0
message_stop — final event, always emitted:
event: message_stop
data: {"type":"message_stop"}error — emitted on stream failures, followed by message_stop:
event: error
data: {"type":"error","error":{"type":"api_error","message":"..."}}
event: message_stop
data: {"type":"message_stop"}Delta Types
delta.type | Additional field | Description |
|---|---|---|
text_delta | text: "..." | Streamed text content |
input_json_delta | partial_json: "..." | Incremental tool call arguments (JSON fragment) |
thinking_delta | thinking: "..." | Extended thinking content |
signature_delta | signature: "..." | Thinking block verification signature |
citations_delta | citation: {...} | Web search citation object |
Built-in Tool IDs
Current Claude Code built-in Anthropic tool IDs accepted by PickLayer include:
bash_20250124,bash_20250728(legacybash_20241022also accepted)text_editor_20250124,text_editor_20250728(legacytext_editor_20241022also accepted)computer_20250124,computer_20250728(legacycomputer_20241022also accepted)web_search_20250305
Count Tokens
POST /v1/messages/count_tokens
{
"model": "openrouter/z-ai/glm-4.7-flash",
"messages": [
{ "role": "user", "content": "Hello world" }
]
}Response schema:
{
"input_tokens": 12,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
}The response body remains Anthropic-compatible. Provider/source accuracy details are recorded in telemetry, not in the payload.
Attaching encrypted files
Canonical-additive extension.
file_keysis a PickLayer-only top-level field. Clients sending canonical Anthropic Messages requests with plaintext files are unaffected — the field has no effect unless a referenced file hasis_encrypted: true. End-to-end file encryption is not part of the canonical Messages API; this field exists to preserve the trust model (UMK never leaves the client) without forking the canonical request shape.
When a Messages request references a PickLayer file with is_encrypted: true (via an image block or a document block whose source.url/source.file_id resolves to an encrypted UUID), the gateway needs the per-file key to decrypt before forwarding plaintext to the upstream provider.
Pass a top-level file_keys object on the request body:
{
"model": "anthropic/claude-sonnet-4-5-20250929",
"max_tokens": 1024,
"messages": [{
"role": "user",
"content": [
{ "type": "text", "text": "What animal is in this image?" },
{ "type": "image",
"source": { "type": "url", "url": "6fefb90d-71fe-49ab-8f8f-2c5b4f2e0a01" } }
]
}],
"file_keys": {
"6fefb90d-71fe-49ab-8f8f-2c5b4f2e0a01": "Ei5dZbVaKQlnhtdtKuecl78o95fryNJPUvcQ9mSmk3c"
}
}- Key — PickLayer file UUID referenced anywhere in the request.
- Value — base64url-encoded per-file key derived as
HKDF-SHA256(UMK, "picklayer:file:" + file_id). - Plaintext files do not appear here; a stray entry returns
400 unexpected_file_key. - Lifetime — request-scoped, transient. Never persisted to logs (logger redaction enforced).
Decryption path selection
The server picks one of two methods per file based on size and provider capability:
| Method | When | Wire shape |
|---|---|---|
| Inline base64 | Small files, Anthropic accepts inline | source: { type: "base64", media_type, data } |
| Signed CDN URL | Large files, Anthropic URL form supported | URL with opaque &efk=… query param; origin decrypts on fetch |
The plaintext file_key is never present in the signed URL — only the AES-256-GCM-wrapped form (efk).
Failure modes
| Condition | HTTP | code | param |
|---|---|---|---|
Encrypted UUID, no file_keys entry | 400 | missing_file_key | file_keys.<uuid> |
| Wrong key (fingerprint mismatch) | 400 | e2ee_invalid_file_key | file_keys.<uuid> |
| Plaintext UUID with stray entry | 400 | unexpected_file_key | file_keys.<uuid> |
| Legacy V1 encryption | 400 | e2ee_legacy_encryption | file_keys.<uuid> |
Tampered efk on signed URL | 400 (origin) | e2ee_invalid_file_key | — |
The gateway fails closed — the upstream provider is not called, no billing event is generated.
See Encryption — Attaching encrypted files to chat requests for the full architectural reference, cross-session re-derivation flow, and SDK examples.
Preview Auth (Composite x-api-key)
When preview auth is enabled on the deployment, Claude Code CLI can pack both the JWT and preview secret into a single x-api-key value:
export ANTHROPIC_API_KEY="<PL-JWT>::preview::<preview-secret>"The ::preview:: delimiter lets the server unpack both values. The JWT authenticates the user; the preview token passes the preview gate.