API reference

Everything is served from https://api.zenllm.org and authenticated with your API key. OpenAI-compatible, Anthropic-compatible, one catalog.

Introduction

ZenLLM is one API in front of every frontier model, with automatic provider failover and prepaid per-token billing. The base URL for everything is https://api.zenllm.org.

The API is wire-compatible with the OpenAI and Anthropic SDKs: point your existing client at the base URL and keep your code. Streaming uses standard SSE, tool calling passes through natively, and responses always echo the model id you asked for, whichever upstream served it.

curl https://api.zenllm.org/v1/chat/completions \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-fable-5",
    "messages": [{"role": "user", "content": "Hello"}],
    "stream": true
  }'

Authentication

Create a key in the dashboard. Keys are shown once and stored hashed; each key can carry its own model allowlist, rate limit, IP allowlist, total spend cap and expiry.

Send it on any endpoint using whichever header your SDK already uses.

# Any one of these works on every endpoint
Authorization: Bearer sk-zenllm-...
x-api-key: sk-zenllm-...
anthropic-api-key: sk-zenllm-...   # for Anthropic SDKs

Errors and limits

Errors are JSON with a stable shape and never leak upstream provider details. Account rate limits scale with your balance plus lifetime spend (spending credits never lowers your tier): $5+ gives 15 rpm, $10+ 30 rpm, $50+ 70 rpm, $100+ 135 rpm and $200+ 250 rpm; contact support for more.

Failed requests are refunded automatically: billing settles only on completed responses.

Parameters
401auth_error
Missing or invalid API key, or an IP outside the key's allowlist.
402billing_error
Balance too low, or a key/account spend limit was reached.
404not_found
Unknown model id or route.
429rate_limit_error
Key RPM or account tier exceeded. Honor Retry-After with jitter.
502upstream_error
Every provider in the model's chain failed. Safe to retry.
{
  "error": {
    "message": "Insufficient credits",
    "type": "billing_error",
    "code": 402
  }
}

Overview

Anything that speaks the OpenAI or Anthropic APIs works unchanged, so every tool below connects with just a base URL and a key. Any model id from /v1/models works in any tool: run Claude in an OpenAI client or GPT in an Anthropic one, the router translates.

CORS is open on the /v1 surface, so browser-based tools can call the API directly with your key; no proxy needed. Create your key in the dashboard first, it is shown once.

SillyTavern

The most common RP frontend. Connects through its Chat Completion API.

  1. 1Open the plug icon (API Connections).
  2. 2Set API to Chat Completion and the source to Custom (OpenAI-compatible).
  3. 3Paste the endpoint https://api.zenllm.org/v1 and your key, then press Connect.
  4. 4Pick any model from the list; it loads straight from the live catalog.

Presets work as-is, including prompt post-processing, system prompts and assistant prefill.

API: Chat Completion
Chat Completion Source: Custom (OpenAI-compatible)
Custom Endpoint (Base URL): https://api.zenllm.org/v1
API Key: sk-zenllm-...
Model: claude-fable-5   # list fills from /v1/models

JanitorAI

JanitorAI calls the API from your browser, which works because CORS is open on /v1. Your key stays in your browser's local storage.

  1. 1Open a chat and go to API Settings.
  2. 2Choose Proxy, then configuration Custom.
  3. 3Set the proxy URL to https://api.zenllm.org/v1/chat/completions (the full path, not just the base).
  4. 4Enter your key and a model name, save, then use "Check proxy" to verify.
API: Proxy
Configuration: Custom
Model name: claude-fable-5
Proxy URL: https://api.zenllm.org/v1/chat/completions
API Key: sk-zenllm-...

Marinara Engine

The local chat, roleplay and game engine. Its custom provider speaks the OpenAI format, and connections can be overridden per chat, so different characters can run on different models.

  1. 1Open Connections and add a new connection.
  2. 2Choose Custom (OpenAI-compatible) as the provider.
  3. 3Set the base URL to https://api.zenllm.org/v1 and paste your key.
  4. 4The model picker fills itself from the catalog; pick one and save.
Connections -> Add Connection
Provider: Custom (OpenAI-compatible)
Base URL: https://api.zenllm.org/v1
API Key: sk-zenllm-...

# the model picker fills itself from /v1/models

Agnai and Risu

Both connect through their OpenAI-compatible option. Agnai takes the base URL; Risu wants the full chat completions path.

  1. 1Agnai: in your preset, pick the OpenAI-compatible (third party) service and set the API URL to https://api.zenllm.org/v1.
  2. 2Risu: under Settings, choose the custom OpenAI-compatible model and set the request URL to https://api.zenllm.org/v1/chat/completions.
  3. 3Paste your key and type any model id from the catalog.
Preset -> AI Service: OpenAI-compatible / third party
API URL: https://api.zenllm.org/v1
API Key: sk-zenllm-...
Model: claude-fable-5

Claude Code

Claude Code speaks the Anthropic Messages API, which ZenLLM serves natively at /v1/messages, streaming, tool use and token counting included.

  1. 1Export ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN in your shell, or put them in ~/.claude/settings.json to make it permanent.
  2. 2Optionally pin ANTHROPIC_MODEL to any catalog id.
  3. 3Run claude. No login needed; the key is the auth.
export ANTHROPIC_BASE_URL="https://api.zenllm.org"
export ANTHROPIC_AUTH_TOKEN="sk-zenllm-..."
export ANTHROPIC_MODEL="claude-fable-5"
claude

Codex CLI

Codex talks the Responses API, served at /v1/responses. Point it at ZenLLM with a custom model provider.

  1. 1Add the provider block to ~/.codex/config.toml.
  2. 2Export ZENLLM_API_KEY in your shell.
  3. 3Run codex. Switch models any time with /model or the model line in the config.
# ~/.codex/config.toml
model = "gpt-5.6"
model_provider = "zenllm"

[model_providers.zenllm]
name = "ZenLLM"
base_url = "https://api.zenllm.org/v1"
env_key = "ZENLLM_API_KEY"
wire_api = "responses"

opencode

Register ZenLLM as a provider through the OpenAI-compatible SDK adapter.

  1. 1Add the provider block to ~/.config/opencode/opencode.json (or a project-local opencode.json).
  2. 2Export ZENLLM_API_KEY, or paste the key inline if you prefer.
  3. 3Run opencode and pick a model with /models.
// ~/.config/opencode/opencode.json
{
  "provider": {
    "zenllm": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "ZenLLM",
      "options": {
        "baseURL": "https://api.zenllm.org/v1",
        "apiKey": "{env:ZENLLM_API_KEY}"
      },
      "models": {
        "claude-fable-5": { "name": "Claude Fable 5" },
        "gpt-5.6": { "name": "GPT-5.6" }
      }
    }
  }
}

Zed

Zed's agent panel supports OpenAI-compatible providers natively.

  1. 1Add the provider under language_models.openai_compatible in your settings.json, listing the models you want.
  2. 2Open the agent panel's settings, find ZenLLM and paste your key when prompted.
  3. 3Select the model from the panel's model picker.
// Zed settings.json
{
  "language_models": {
    "openai_compatible": {
      "ZenLLM": {
        "api_url": "https://api.zenllm.org/v1",
        "available_models": [{
          "name": "claude-fable-5",
          "display_name": "Claude Fable 5",
          "max_tokens": 1000000
        }]
      }
    }
  }
}

Cursor

Cursor can route its chat models through any OpenAI-compatible endpoint via the base URL override.

  1. 1Open Cursor Settings, then Models.
  2. 2Paste your ZenLLM key into the OpenAI API key field and enable the base URL override with https://api.zenllm.org/v1.
  3. 3Add the catalog ids you want as custom model names, then hit Verify.

Note: the override applies to chat. Cursor-native features like Tab completions still run on Cursor's own service.

Cursor Settings -> Models
OpenAI API Key: sk-zenllm-...
Override OpenAI Base URL: https://api.zenllm.org/v1
Model names: claude-fable-5, gpt-5.6, ...

Cline and Continue

The two big VS Code agents, both with first-class OpenAI-compatible support.

  1. 1Cline: in provider settings choose OpenAI Compatible, set the base URL, key and a model id.
  2. 2Continue: add a model entry with provider: openai and apiBase to ~/.continue/config.yaml.
API Provider: OpenAI Compatible
Base URL: https://api.zenllm.org/v1
API Key: sk-zenllm-...
Model ID: claude-fable-5

LibreChat and Open WebUI

Self-hosted chat UIs. Both can fetch the model list from the catalog, so new models appear without config changes.

  1. 1LibreChat: add a custom endpoint to librechat.yaml with fetch: true and restart.
  2. 2Open WebUI: in Admin Panel connections, enable the OpenAI API and set the base URL and key.
# librechat.yaml
endpoints:
  custom:
    - name: ZenLLM
      baseURL: https://api.zenllm.org/v1
      apiKey: "${ZENLLM_API_KEY}"
      models:
        default: ["claude-fable-5", "gpt-5.6"]
        fetch: true

Chat completions

POST/v1/chat/completions

The OpenAI-compatible endpoint and the one most SDKs use. On failure the router retries the model's next provider inside the same request, so a single call survives a provider outage.

Billing settles on completion: with stream: true the final SSE chunk before [DONE] carries the usage object, and that is what your balance is charged. If you cancel mid-stream you pay only for the tokens generated up to the cancel; requests that fail outright cost nothing. Every response carries an x-zenllm-request-id header you can quote to support and match against your ledger.

Parameters
modelstringrequired
Any id from /v1/models, e.g. claude-fable-5.
messagesarrayrequired
Chat history: {role, content}. Content can be text or multimodal parts (vision models accept image parts).
streamboolean
true streams SSE chunks; the final chunk carries usage.
max_tokensinteger
Output cap. Defaults to the model's maximum.
temperaturenumber
0 to 2. Passed through to the model untouched.
tools / tool_choicearray / string
Standard OpenAI tool calling, passed through natively. No translation shims.
response_formatobject
e.g. {"type": "json_object"} on models that support JSON mode.
curl https://api.zenllm.org/v1/chat/completions \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-fable-5",
    "messages": [{"role": "user", "content": "Hello"}],
    "stream": true
  }'

Messages

POST/v1/messages

Native Anthropic Messages format, including system prompts, thinking and tool use. Works unchanged with the official Anthropic SDKs. POST /v1/messages/count_tokens counts without running.

Parameters
modelstringrequired
Any chat model id from the catalog.
max_tokensintegerrequired
Required by the Messages format.
messagesarrayrequired
User and assistant turns, Anthropic content blocks supported.
systemstring
System prompt, kept out of the messages array.
thinkingobject
Extended thinking config for models that support it.
streamboolean
Server-sent events in Anthropic's event framing.
curl https://api.zenllm.org/v1/messages \
  -H "x-api-key: $ZENLLM_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-fable-5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Hello"}]
  }'

Responses

POST/v1/responses

The OpenAI Responses format. The router converts between chat and responses dialects when a provider only supports one, so any model works from any format.

Parameters
modelstringrequired
Model id. The router converts dialects when the upstream only speaks chat.
inputstring | arrayrequired
Prompt text or structured input items.
streamboolean
Streams response events as SSE.
reasoningobject
Effort hints for reasoning models that accept them.
curl https://api.zenllm.org/v1/responses \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6",
    "input": "Summarize the attached notes.",
    "stream": true
  }'

Embeddings and rerank

POST/v1/embeddings

OpenAI-compatible embeddings plus POST /v1/rerank for scoring a document list against a query in retrieval pipelines.

Parameters
modelstringrequired
text-embedding-3-large.
inputstring | arrayrequired
One string or a batch. Billed on total input tokens.
top_ninteger
Rerank only: how many documents to return, best first.
curl https://api.zenllm.org/v1/embeddings \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "text-embedding-3-large",
    "input": "The quick brown fox"
  }'

Images

POST/v1/images/generations

Image generation and edits. Generations take JSON; POST /v1/images/edits takes multipart form data with an input image.

Parameters
modelstringrequired
gpt-image-2.
promptstringrequired
What to draw. For edits, how to change the input image.
sizestring
e.g. 1024x1024. Model default when omitted.
imagefile
Edits only: multipart input image.
curl https://api.zenllm.org/v1/images/generations \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "isometric server rack, warm charcoal, orange accents",
    "size": "1024x1024"
  }'

Audio

POST/v1/audio/speech

Text to speech returns raw audio bytes; POST /v1/audio/transcriptions takes multipart audio and returns text.

Parameters
inputstringrequired
Speech: the text to speak.
voicestring
Speech: voice preset, e.g. alloy.
filefilerequired
Transcriptions: multipart audio (mp3, m4a, wav, webm).
languagestring
Transcriptions: ISO hint, auto-detected when omitted.
curl https://api.zenllm.org/v1/audio/speech \
  -H "Authorization: Bearer $ZENLLM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "tts", "input": "Deploy is live.", "voice": "alloy"}' \
  --output speech.mp3

Models and health

GET/v1/models

The catalog with per-model pricing and context windows, no auth required. The detail route also lists the model's provider chain. GET /health reports service status.

curl https://api.zenllm.org/v1/models

# per-model detail incl. provider chain
curl https://api.zenllm.org/v1/models/claude-fable-5

# service health
curl https://api.zenllm.org/health
Something missing? Every endpoint mirrors its upstream format exactly, so the OpenAI and Anthropic docs apply for the details of each body.