API reference
Every request carries the caller's own provider API key. Two different callers' identical requests never share a cache entry — the cache key is owner-scoped, not just content-addressed.
Chat completions
POST /{provider}/v1/chat/completions
Authorization: Bearer <caller's own key for that provider>
{ "model": "...", "messages": [...], "temperature": 0 } # OpenAI chat shape, forwarded verbatim on a miss
→ the upstream response body, unchanged # 200 on a cache hit, ~1 ms
X-Zerocache-Completion-Hit: true | false
X-Zerocache-Completion-Hit-Kind: exact | semantic # on a hit
X-Zerocache-Semantic-Score: 0.982 # on a semantic hitOnly deterministic requests are cached: temperature: 0 or an explicit seed, with n absent or 1. Anything else is a transparent passthrough — forwarded, nothing stored, nothing counted. A non-2xx upstream response is forwarded with its real status and never cached. The cache key is the canonicalized body: blind to user / stream / metadata / key order / number spelling.
stream: true works — the SSE is buffered on a miss (streamed to the caller live meanwhile) and replayed frame-by-frame on a hit. One entry serves both streaming and non-streaming callers.
{provider} is any of openai, mistral,gemini, groq, deepseek, together,openrouter, xai, fireworks, plus anything added viaZEROCACHE_CHAT_PROVIDERS.
Anthropic Messages
POST /{provider}/v1/messages
Authorization: Bearer <caller's own Anthropic key> # rewritten to x-api-key upstream
anthropic-version: 2023-06-01 # optional; folded into the cache key
{ "model": "...", "max_tokens": 512, "messages": [...], "temperature": 0 }
→ the upstream response body, unchanged
X-Zerocache-Completion-Hit: true | false
X-Zerocache-Completion-Hit-Kind: exactClaude's native /v1/messages shape. Cache gate is temperature: 0only (Anthropic has no seed / n), plus non-emptymessages and thinking absent or disabled.anthropic-version (default 2023-06-01) and anthropic-betaare folded into the cache key. stream: true is a raw passthrough.
Text embeddings
POST /{provider}/v1/embeddings
Authorization: Bearer <caller's real provider API key>
{ "model": "<real upstream model name>", "input": ["text1", "text2"] }Response:
{
"object": "list",
"data": [{ "embedding": [...], "index": 0 }],
"model": "...",
"usage": {...}
}input accepts either a JSON array of strings or a single bare JSON string — both are valid, matching OpenAI's real input: string | string[] contract.
Cloud provider routing
Azure, Bedrock, and Vertex AI each front several model vendors behind one API, so routing coordinates (region, project, input/task type) are encoded into the model string itself — no new wire field, since model is already free-form per request and already lands in the cache key. azure is only a legal {provider}once an Azure base-URL env var is set; otherwise it 404s like any unknown provider.
| {provider} | model grammar | example |
|---|---|---|
bedrock | [<region>/]<modelId>[#<input_type>] | us-east-1/cohere.embed-english-v3#search_query |
vertexai | [<location>/<project>/]<modelId>[#<task_type>] | us-central1/my-proj/text-embedding-005#RETRIEVAL_DOCUMENT |
azure | [foundry:]<deployment>[#<input_type>] | foundry:cohere-embed-v3-english#document |
Image embeddings (Gemini only)
POST /gemini/v1/images/embeddings
Authorization: Bearer <caller's real Gemini API key>
{ "model": "gemini-embedding-2", "input": ["data:image/png;base64,<...>"] }Each input entry is a data:<mime_type>;base64,<data> URI. No other provider implements image embeddings — they 404 with a message naming exactly what's unsupported.
Deleting cache entries
DELETE /{provider}/v1/embeddings
Authorization: Bearer <caller's real provider API key>
{ "model": "...", "input": ["text1", "text2"] }
→ { "deleted": 2 }Chat completions and messages have matching DELETE routes:
DELETE /{provider}/v1/{chat/completions,messages}
Authorization: Bearer <caller's own key>
{ "model": "...", "messages": [...] }
→ { "deleted": 1 }The count reflects how many keys were requested for deletion, not how many actually existed — deletion is idempotent and owner-scoped.
Error shapes
| Status | Meaning |
|---|---|
401 | Missing or malformed Authorization header |
404 | Unknown {provider}, or a provider that doesn't support images/embeddings on this route |
422 | Valid JSON, missing or wrong-typed required field |
400 | JSON that isn't syntactically valid, or a malformed image data URI |
Every error path returns the same { "error": "..." } JSON shape.
Response headers
Embeddings: X-Zerocache-Hits / X-Zerocache-Misseson every response. usage reflects only tokens actually billed for this request's misses — 0 for an all-hit batch, 0 for a coalesced request that piggybacked, and always 0 for Gemini, which never reports usage.
Chat & messages: X-Zerocache-Completion-Hit: true | false; on a hit, X-Zerocache-Completion-Hit-Kind: exact | semantic, and on a semantic hit, X-Zerocache-Semantic-Score.
Operational endpoints
Unauthenticated, outside the versioned API contract:
| Endpoint | Purpose |
|---|---|
GET /health | Liveness. Zero I/O — proves only that the process/router is up. |
GET /ready | Readiness. 503 on a store-level error; a cache miss on the sentinel key is healthy. |
GET /metrics | Prometheus text: embedding hits/misses/tokens (provider + content_type labels); completion hits/misses/tokens-saved (provider + stream labels); cross-replica coalesced counts; semantic index events. |
GET /dashboard | The live savings SPA, embedded in the binary, polling /metrics. |