Documentation
OmniAPI speaks two protocols on the same host: the OpenAI chat-completions format, and Anthropic's messages format. One key works on both, spends the same wallet, and counts against the same limits — pick whichever one your tool already knows how to talk.
Start here
- 1. Create an account and open Dashboard → API keys.
- 2. Generate a key. Copy it — it is shown once and stored only as a hash.
- 3. Pick the base URL that matches your client and export both values.
export OMNI_API_KEY="omni_live_xxxxxxxxxxxx" # OpenAI-format clients (Codex, Cline, OpenAI SDKs, curl) export OMNI_BASE_URL="https://omniapi.lat/api/v1" # Anthropic-format clients (Claude Code, Anthropic SDKs) export ANTHROPIC_BASE_URL="https://omniapi.lat/api/anthropic"
Base URLs
Same host, same key, same balance. The only difference is the shape of the JSON on the wire — which is decided by your client, not by you.
OpenAI format
https://omniapi.lat/api/v1
POST /chat/completions, GET /models. Use it for Codex CLI, Cline, Roo Code, Continue, LiteLLM, LangChain, and the OpenAI SDK in any language.
Anthropic format
https://omniapi.lat/api/anthropic
POST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models. Use it for Claude Code and the Anthropic SDKs.
Why two and not one: Claude Code does not speak chat-completions. It sends POST /v1/messages with a different request body, a different streaming event format and a different error shape. A single OpenAI-only endpoint cannot serve it, so the gateway translates between the two formats in both directions and bills the result identically either way.
Authentication
Send your key as a bearer token. x-api-key is also accepted, which is what the Anthropic SDKs send by default — so both surfaces work with whichever header your client prefers.
Authorization: Bearer omni_live_xxxxxxxxxxxx # or x-api-key: omni_live_xxxxxxxxxxxx
Keys are stored as SHA-256 hashes. A lost key cannot be recovered, only revoked and replaced. Treat a key like a password: it can spend your balance.
Claude Code
Two environment variables and nothing else. ANTHROPIC_AUTH_TOKEN carries your OmniAPI key; Claude Code sends it as a bearer token, which this gateway accepts.
export ANTHROPIC_BASE_URL="https://omniapi.lat/api/anthropic" export ANTHROPIC_AUTH_TOKEN="$OMNI_API_KEY" claude
Put those two lines in your ~/.bashrc, ~/.zshrc or PowerShell profile to make it permanent. On Windows PowerShell:
$env:ANTHROPIC_BASE_URL = "https://omniapi.lat/api/anthropic" $env:ANTHROPIC_AUTH_TOKEN = "omni_live_xxxxxxxxxxxx" claude
Claude Code asks for models by their Anthropic names — claude-opus-*, claude-sonnet-*, claude-haiku-*. Those resolve to the matching route automatically, so you do not have to change the model setting. To pin one explicitly, set ANTHROPIC_MODEL to any route or alias below.
If you already have ANTHROPIC_API_KEY set for a direct Anthropic account, unset it — Claude Code prefers it over the auth token and your requests will go to Anthropic instead of here.
Codex CLI
Codex reads providers from ~/.codex/config.toml. Add OmniAPI as one and select it. wire_api = "chat" matters: it tells Codex to use chat-completions rather than the Responses API.
# ~/.codex/config.toml model = "gpt-5-codex" model_provider = "omniapi" [model_providers.omniapi] name = "OmniAPI" base_url = "https://omniapi.lat/api/v1" env_key = "OMNI_API_KEY" wire_api = "chat"
Then export the key in the same shell you run codex from — the env_key line above is what tells Codex which variable to read.
export OMNI_API_KEY="omni_live_xxxxxxxxxxxx" codex
Cline, Roo Code, Continue
All three have an OpenAI Compatible provider. The settings are always the same three fields:
| Provider | OpenAI Compatible |
| Base URL | https://omniapi.lat/api/v1 |
| API key | omni_live_xxxxxxxxxxxx |
| Model ID | omni-pro (or any alias below) |
Continue uses a YAML config instead of a form:
models:
- name: OmniAPI
provider: openai
model: omni-pro
apiBase: https://omniapi.lat/api/v1
apiKey: omni_live_xxxxxxxxxxxxSDKs & curl
OpenAI SDK (Python)
from openai import OpenAI
client = OpenAI(
api_key="omni_live_xxxxxxxxxxxx",
base_url="https://omniapi.lat/api/v1",
)
resp = client.chat.completions.create(
model="omni-pro",
messages=[{"role": "user", "content": "Explain rolling windows."}],
)
print(resp.choices[0].message.content)OpenAI SDK (Node)
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.OMNI_API_KEY,
baseURL: "https://omniapi.lat/api/v1",
});
const resp = await client.chat.completions.create({
model: "omni-pro",
messages: [{ role: "user", content: "Explain rolling windows." }],
});
console.log(resp.choices[0].message.content);Anthropic SDK (Python)
from anthropic import Anthropic
client = Anthropic(
api_key="omni_live_xxxxxxxxxxxx",
base_url="https://omniapi.lat/api/anthropic",
)
msg = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Explain rolling windows."}],
)
print(msg.content[0].text)curl — OpenAI format
curl "https://omniapi.lat/api/v1/chat/completions" \
-H "Authorization: Bearer $OMNI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "omni-pro",
"messages": [
{"role": "system", "content": "You are concise."},
{"role": "user", "content": "Explain rolling windows."}
],
"temperature": 0.4
}'curl — Anthropic format
curl "https://omniapi.lat/api/anthropic/v1/messages" \
-H "x-api-key: $OMNI_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [
{"role": "user", "content": "Explain rolling windows."}
]
}'Response (OpenAI format)
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "omni-pro",
"choices": [
{ "index": 0,
"message": {"role": "assistant", "content": "..."},
"finish_reason": "stop" }
],
"usage": {
"prompt_tokens": 42,
"completion_tokens": 118,
"total_tokens": 160
}
}Models & aliases
Seven routes, priced per million tokens. GET /api/v1/models returns them in OpenAI's shape with pricing; GET /api/anthropic/v1/models returns the same list in Anthropic's shape.
| Route | Family | Context | In / 1M | Out / 1M |
|---|---|---|---|---|
| omni-max | Frontier | 200K | $30 | $150 |
| omni-pro | Frontier | 200K | $6 | $30 |
| omni-fast | Balanced | 128K | $1.6 | $8 |
| omni-mini | Economy | 128K | $0.5 | $2.5 |
| omni-vision | Multimodal | 128K | $5 | $20 |
| omni-embed | Embeddings | 8K | $0.04 | $0 |
| omni-code | Specialist | 256K | $3 | $15 |
Provider names work too
Coding agents send the model name they know, not ours. Those are matched by pattern and resolved to a route, so a model released next month still works instead of returning a 404. Billing always uses the route it resolved to — asking for a cheap-sounding name never gets you an expensive model at the cheap price.
| You send | Routes to |
|---|---|
| claude-opus-4-6 | omni-max |
| claude-sonnet-4-6 | omni-pro |
| claude-haiku-4-5 | omni-fast |
| gpt-5 | omni-pro |
| gpt-5-codex | omni-code |
| gpt-5-mini | omni-fast |
| gemini-2.5-pro | omni-pro |
The rule behind the table: opus → omni-max, sonnet → omni-pro, haiku / mini / nano / flash → omni-fast, codex → omni-code, anything with embed in it → omni-embed. Everything else lands on omni-pro.
Streaming
Set "stream": true on either surface. Tokens are relayed as they arrive, and the response is metered exactly once when the stream ends — including when you cancel it half way, so a stopped generation still bills for what was already produced.
OpenAI format
data: {"choices":[{"delta":{"content":"Rolling"}}]}
data: {"choices":[{"delta":{"content":" windows"}}]}
data: {"choices":[{"delta":{},"finish_reason":"stop"}],"usage":{...}}
data: [DONE]Anthropic format
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Rolling"}}
event: message_stop
data: {"type":"message_stop"}Tool calls stream too, in whichever dialect you asked for — arguments arrive as tool_calls[].function.arguments fragments on the OpenAI side and input_json_delta blocks on the Anthropic side.
Limits & headers
Two things gate a request: your wallet must have credit, and your plan's rolling 5-hour and 7-day spend allowances must have room. Both are checked before anything is forwarded upstream, and a rejected request is never billed — neither is a request that the upstream provider fails.
x-omni-balance-usd
Wallet credit remaining after this request was charged.
x-omni-cost-usd
What this single request cost you.
x-omni-window-5h-remaining
USD left in the 5-hour window, returned on every response.
x-omni-window-7d-remaining
USD left in the weekly window, returned on every response.
Windows are rate limits, not an allowance of free money — real spend always comes out of the wallet. See pricing for the per-plan figures.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Key missing, malformed, or revoked. |
| 402 | insufficient_balance | Wallet credit is exhausted. |
| 404 | model_not_found | No route matched that model name. |
| 429 | window_exceeded | 5-hour or weekly allowance used up. |
| 502 | upstream_error | The upstream provider failed. Not billed. |
| 503 | not_configured | No upstream is configured for that route. |
On /api/v1 errors come back in OpenAI's shape:
{
"error": {
"type": "window_exceeded",
"message": "5-hour usage window exhausted.",
"window": "5h",
"retry_after_seconds": 4820
}
}On /api/anthropic they come back in Anthropic's shape, so SDK error handling keeps working:
{
"type": "error",
"error": {
"type": "window_exceeded",
"message": "5-hour usage window exhausted."
}
}Something not covered here? Get in touch.