Skip to Content
Messages API

Messages API

PickLayer exposes an Anthropic Messages-compatible surface at:

  • POST /v1/messages
  • POST /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-flash
  • openai/gpt-5.2
  • gemini/gemini-2.5-flash
  • xai/grok-4
  • anthropic/claude-sonnet-4-5-20250929

PickLayer-specific parameters

ParameterTypeDefaultDescription
service_tierstring—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_stop

On 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 present
  • cache_creation_input_tokens — present when greater than 0
  • cache_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.typeAdditional fieldDescription
text_deltatext: "..."Streamed text content
input_json_deltapartial_json: "..."Incremental tool call arguments (JSON fragment)
thinking_deltathinking: "..."Extended thinking content
signature_deltasignature: "..."Thinking block verification signature
citations_deltacitation: {...}Web search citation object

Built-in Tool IDs

Current Claude Code built-in Anthropic tool IDs accepted by PickLayer include:

  • bash_20250124, bash_20250728 (legacy bash_20241022 also accepted)
  • text_editor_20250124, text_editor_20250728 (legacy text_editor_20241022 also accepted)
  • computer_20250124, computer_20250728 (legacy computer_20241022 also 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_keys is 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 has is_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:

MethodWhenWire shape
Inline base64Small files, Anthropic accepts inlinesource: { type: "base64", media_type, data }
Signed CDN URLLarge files, Anthropic URL form supportedURL 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

ConditionHTTPcodeparam
Encrypted UUID, no file_keys entry400missing_file_keyfile_keys.<uuid>
Wrong key (fingerprint mismatch)400e2ee_invalid_file_keyfile_keys.<uuid>
Plaintext UUID with stray entry400unexpected_file_keyfile_keys.<uuid>
Legacy V1 encryption400e2ee_legacy_encryptionfile_keys.<uuid>
Tampered efk on signed URL400 (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.

Last updated on