# Prism > An OpenAI-compatible LLM gateway. One model name, several vendors behind it. > When a vendor runs out of budget, rate-limits, or goes down, Prism fails over > to the next one mid-request without the client noticing. Base URL: `https://prism.rodmena.co.uk/v1` This doc: `https://prism.rodmena.co.uk/llms.txt` (served by the API, no auth) Health: `https://prism.rodmena.co.uk/readiness` (no auth) Everything below was exercised against the live gateway with the real client binaries (opencode, codex, claude), most recently 2026-08-09. Where something does not work, this document says so instead of guessing. ## Authentication Standard bearer auth. Your key looks like `rak_...`. ``` Authorization: Bearer ``` Anthropic-wire clients (Claude Code) send `x-api-key` instead. Prism accepts either. ## Models | model | context | max output | vendors behind it | |---|---|---|---| | `deepseek-v4-flash` | 1,048,576 | 65,536 | ollama, opencode, deepseek, openrouter, + 2 free last-resort | | `glm-5.2` | 1,048,576 | 65,536 | ollama, opencode, openrouter | | `gemini-3-pro` | 1,048,576 | 65,535 | antigravity (Gemini 3.1 Pro) | | `gemini-3-flash` | 1,048,576 | 65,536 | antigravity (Gemini 3 Flash) | | `claude-opus-4.6` | 250,000 | 64,000 | antigravity (Claude Opus 4.6, thinking) | | `claude-sonnet-4.6` | 250,000 | 64,000 | antigravity (Claude Sonnet 4.6, thinking) | | `glm-4.7-flash-free` | 200,000 | 65,536 | zenmux (free tier) | `GET /v1/models` returns these with `max_model_len`. **Configure your client with the real numbers.** A client that believes the window is smaller will compact the conversation every few seconds. ## Endpoints | endpoint | wire format | status | |---|---|---| | `POST /v1/chat/completions` | OpenAI Chat Completions | works, streaming and non-streaming | | `POST /v1/responses` | OpenAI Responses | works, streaming and non-streaming | | `POST /v1/completions` | OpenAI legacy completions | works | | `GET /v1/models` | — | works | | `POST /v1/messages` | Anthropic Messages | works, streaming and non-streaming (Claude Code) | | `GET /usage` (`/v1/usage`) | Prism | per-model usage, remaining and status — authenticated (CLI: `prism-launch --usage`) | | `GET /llms.txt` | — | this document, unauthenticated | | `GET /prism-launch` | — | the launcher script, unauthenticated — see *Claude Code* below | Prism is **stateless**. `previous_response_id` is rejected; `store` and `background` are accepted only as `false`; `truncation` only as `disabled`. ## Reasoning effort Pass `reasoning_effort` on `/v1/chat/completions` (or `reasoning.effort` on `/v1/responses`). It is forwarded to whichever vendor serves the request. DeepSeek's levels are **`none`, `high`, `max`** — not the generic low/medium/high. Unknown parameters are forwarded verbatim rather than dropped, so a vendor-specific setting reaches the vendor. Reasoning text is streamed under **both** `reasoning` and `reasoning_content`, because different SDKs read different spellings. --- # Client setup ## opencode — works Edit `~/.config/opencode/opencode.json`: ```json { "provider": { "prism": { "npm": "@ai-sdk/openai-compatible", "name": "Prism", "options": { "baseURL": "https://prism.rodmena.co.uk/v1", "apiKey": "" }, "models": { "deepseek-v4-flash": { "name": "Prism/deepseek-v4-flash", "limit": { "context": 1048576, "output": 65536 }, "variants": { "none": { "reasoningEffort": "none" }, "high": { "reasoningEffort": "high" }, "max": { "reasoningEffort": "max" } } }, "glm-5.2": { "name": "Prism/glm-5.2", "limit": { "context": 1048576, "output": 65536 }, "variants": { "none": { "reasoningEffort": "none" }, "max": { "reasoningEffort": "max" } } }, "gemini-3-pro": { "name": "Prism/gemini-3-pro", "limit": { "context": 1048576, "output": 65535 } }, "gemini-3-flash": { "name": "Prism/gemini-3-flash", "limit": { "context": 1048576, "output": 65536 } }, "claude-opus-4.6": { "name": "Prism/claude-opus-4.6", "limit": { "context": 250000, "output": 64000 } }, "claude-sonnet-4.6": { "name": "Prism/claude-sonnet-4.6", "limit": { "context": 250000, "output": 64000 } } } } } } ``` Antigravity-backed models (`gemini-*`, `claude-*`) always reason — thinking is on by default and surfaces as `reasoning_content`. They take no `reasoningEffort` variants, so omit that block for them. Reference the model as `prism/deepseek-v4-flash` anywhere opencode takes a model id (agents, compaction, subagents). The `variants` block is what puts thinking levels in the model picker. Use the vendor's own level names — DeepSeek does not accept `low`/`medium`. ### `oh-my-openagent.json` — if you use the oh-my-opencode plugin The provider block above makes Prism *available*. It does not make anything *use* it. If you run the `oh-my-openagent` plugin, its own routing table decides which model each named agent and each task category gets, and it is a separate file: `~/.config/opencode/oh-my-openagent.json`. Configure the provider and stop there, and every agent keeps running on whatever it pointed at before — nothing errors, nothing warns, and your traffic quietly never reaches Prism. ```json { "$schema": "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json", "agents": { "sisyphus": { "model": "prism/deepseek-v4-flash" }, "sisyphus-junior": { "model": "prism/deepseek-v4-flash" }, "oracle": { "model": "prism/deepseek-v4-flash" }, "librarian": { "model": "prism/deepseek-v4-flash" }, "explore": { "model": "prism/deepseek-v4-flash" }, "multimodal-looker": { "model": "prism/deepseek-v4-flash" }, "prometheus": { "model": "prism/deepseek-v4-flash" }, "metis": { "model": "prism/deepseek-v4-flash" }, "momus": { "model": "prism/deepseek-v4-flash" }, "atlas": { "model": "prism/deepseek-v4-flash" } }, "categories": { "visual-engineering": { "model": "prism/deepseek-v4-flash" }, "ultrabrain": { "model": "prism/deepseek-v4-flash" }, "deep": { "model": "prism/deepseek-v4-flash" }, "artistry": { "model": "prism/deepseek-v4-flash" }, "quick": { "model": "prism/deepseek-v4-flash" }, "writing": { "model": "prism/deepseek-v4-flash" }, "unspecified-low": { "model": "prism/deepseek-v4-flash" }, "unspecified-high": { "model": "prism/deepseek-v4-flash" } } } ``` **Update both blocks.** `agents` and `categories` are routed independently — a category left behind sends whole classes of task to the old provider. **The value is the model ID, not the display name.** The ID is `/` from `opencode.json` — `prism/deepseek-v4-flash`, lowercase. The picker shows `Prism/deepseek-v4-flash` with a capital P; that is the *label*, and writing it here will not resolve. Same for `agent.compaction.model` in `opencode.json` itself — compaction runs on whatever that names, so it is worth pointing at Prism too. Rewrite every entry at once: ```bash python3 - <<'PY' import json, pathlib p = pathlib.Path.home() / ".config/opencode/oh-my-openagent.json" d = json.loads(p.read_text()) for block in ("agents", "categories"): for name in d.get(block, {}): d[block][name]["model"] = "prism/deepseek-v4-flash" p.write_text(json.dumps(d, indent=2) + "\n") print("routed:", sum(len(d.get(b, {})) for b in ("agents", "categories")), "entries") PY ``` ## Codex CLI — works (0.146.0+) **Easiest: `prism-launch --client codex`** (same script that launches Claude Code). It writes a `prism-launch` profile + model catalog into `~/.codex/` and runs `codex -p prism-launch`, so your MCP servers and project trust survive. The catalog declares the model's real context window from Prism's `/v1/models` — the manual setup below does not, so Codex assumes a default and compacts a 1M-token model to fit. ```bash prism-launch --client codex # deepseek-v4-flash prism-launch --client codex --model gemini-3-pro prism-launch --client codex -c # continue the most recent session ``` The generated profile is regenerated on every launch (idempotent). Reasoning levels are deliberately empty: Codex's enum (`minimal/low/medium/xhigh/max/ultra`) does not match Prism's per-model sets (deepseek `none/high/max`, gemini `minimal/low/medium/high`), so a picker would produce a 400. `supports_parallel_tool_calls` is false to match the known-good config; Prism's `/v1/responses` tool-call translation is verified for sequential calls, not parallel. Manual setup (what the profile automates): Edit `~/.codex/config.toml`: ```toml model = "deepseek-v4-flash" model_provider = "prism" [model_providers.prism] name = "Prism" base_url = "https://prism.rodmena.co.uk/v1" env_key = "PRISM_API_KEY" wire_api = "responses" ``` Then `export PRISM_API_KEY=rak_...` in your shell profile and run `codex`. Two things that will bite you otherwise: - **`wire_api` must be `responses`.** Codex removed `wire_api = "chat"` in 0.146; it now refuses to start with `` `wire_api = "chat"` is no longer supported ``. - **`Model metadata for 'deepseek-v4-flash' not found` is a harmless warning.** Codex only ships metadata for OpenAI's own models. Add a `[model_providers. prism.models]` entry if you want it quiet; nothing breaks without one. Two Codex tools cannot be served and are dropped, with a warning in Prism's log: - `web_search` — a hosted OpenAI feature; it executes inside OpenAI, and there is nothing at DeepSeek or GLM to execute it. - `multi_agent_v1` (the sub-agent namespace) — its call-name convention is not specified, and guessing it would emit tool calls Codex cannot route back. Everything else works, including the full agentic loop: shell execution, plan updates, and multi-step tool sequences. ## Claude Code — works Claude Code speaks the **Anthropic Messages API**, which Prism serves at `POST /v1/messages` alongside the OpenAI formats. ### The easy way `prism-launch` is the equivalent of `ollama launch claude`. **Install it from this gateway** — one file, stdlib-only Python 3, no dependencies: ```bash mkdir -p ~/.local/bin curl -fsSL https://prism.rodmena.co.uk/prism-launch -o ~/.local/bin/prism-launch chmod +x ~/.local/bin/prism-launch # ensure ~/.local/bin is on PATH, then: export PRISM_API_KEY=rak_... # your key; put it in your shell profile ``` ```bash prism-launch # deepseek-v4-flash, interactive prism-launch --model gemini-3-pro # 1M context prism-launch --model claude-opus-4.6 # 250K context prism-launch --list # routed models and their context windows prism-launch --usage # what each model has used and what is left prism-launch --usage -v # per-provider detail (quota, keys, breakers) prism-launch -c # continue the most recent conversation prism-launch -r [session-id] # resume a session (no id opens the picker) prism-launch -- -p "say pong" # anything after -- goes to claude ``` It reads the context window from `/v1/models` instead of hardcoding it, and refuses to launch against a gateway with no `/v1/messages` rather than letting you discover it as a 404 on every turn. `--model` accepts anything in `--list`, so new models work the day they are routed — no update needed. Requires `claude` (Claude Code) already installed and on PATH. ### By hand ```bash export ANTHROPIC_BASE_URL=https://prism.rodmena.co.uk export ANTHROPIC_AUTH_TOKEN=$PRISM_API_KEY export ANTHROPIC_MODEL=deepseek-v4-flash export ANTHROPIC_SMALL_FAST_MODEL=deepseek-v4-flash export CLAUDE_CODE_MAX_CONTEXT_TOKENS=1048576 unset ANTHROPIC_API_KEY # it would take precedence over AUTH_TOKEN claude ``` **`CLAUDE_CODE_MAX_CONTEXT_TOKENS` is not optional.** Claude Code assumes 200k for a model it does not recognise and compacts the session to fit — on a 1M-token model that is continuous, silent context loss. Same failure as an under-declared `limit.context` in opencode. Extended thinking (`thinking: {type: enabled}`) is translated to the vendor's `reasoning_effort: high`; thinking is streamed back as Anthropic `thinking` blocks. --- # Verify your setup ```bash export PRISM_API_KEY=rak_... # 1. auth and catalog curl -s https://prism.rodmena.co.uk/v1/models \ -H "Authorization: Bearer $PRISM_API_KEY" # 2. a real completion curl -s https://prism.rodmena.co.uk/v1/chat/completions \ -H "Authorization: Bearer $PRISM_API_KEY" -H 'content-type: application/json' \ -d '{"model":"deepseek-v4-flash","max_tokens":16, "messages":[{"role":"user","content":"say pong"}]}' # 3. which vendors are live right now (no auth) curl -s https://prism.rodmena.co.uk/readiness ``` `/readiness` reports, per model, how many vendors are available and the order Prism will try them in. It returns 503 if any model has zero available vendors. # Failure behaviour worth knowing - Failover happens **before the first byte** reaches you. Once a stream starts, it is delivered by that vendor; Prism will not silently restart it midway. - Routing is performance- and budget-aware: vendors are ordered by measured time-to-first-token, and a vendor whose credit balance is near zero is demoted before it starts failing. - A background monitor probes vendors continuously and is restarted by a watchdog if it dies. `/readiness` exposes its heartbeat under `monitor`. - Errors use OpenAI's shape: `{"error": {"message", "type", "code"}}`.