wiring diagram

API

Nergate speaks both wire formats over one pool. The prefix names the protocol; after it the vendor's own path is untouched, so any client works by swapping the base URL and the key.

openaihttps://universalvoronez.tech/openai/v1
anthropichttps://universalvoronez.tech/anthropic

Anthropic SDKs append /v1/messages themselves, which is why their base stops at /anthropic.

Install

One command scans the machine, shows every supported client with a checkbox, and writes the config files for the ones you tick. Everything lands in each client's own config file — no environment variables are touched unless you pick the shell target.

Linux and macOS:

curl -fsSL https://universalvoronez.tech/install.sh | sh

Windows, in PowerShell:

irm https://universalvoronez.tech/install.ps1 | iex

Non-interactive, for scripts and containers:

curl -fsSL https://universalvoronez.tech/install.sh | sh -s -- --key nrg-... --all

Remove it again — every change is reverted from the config files it touched:

curl -fsSL https://universalvoronez.tech/install.sh | sh -s -- --uninstall
--key nrg-...Your key. Prompted for if unset (or NERGATE_API_KEY).
--targets a,bConfigure these clients only: opencode, anthropic, codex, crush, droid, continue, aider, zed, qwen, goose, llm, shell.
--models a,b|allWhich models to write into the multi-model clients (opencode, Crush, Zed, Droid, Continue, llm). Interactive runs ask for all of them or a hand-picked list.
--model <id>Default model for single-model clients (Claude Code, Codex, Aider, Qwen, Goose, Droid). Interactive runs ask with a picker.
--allTake every detected client, skip the prompts.
--uninstallStrip the Nergate block out of the configs it wrote.

Every file is backed up next to itself as *.nergate-backup before it is touched, and only the nergate entries are rewritten — your other providers, models and settings stay exactly as they were.

Quickstart

Issue a key in the console. It is shown once, starts with nrg-, and goes in the Authorization header.

curl https://universalvoronez.tech/openai/v1/chat/completions \
  -H "Authorization: Bearer nrg-..." \
  -H "content-type: application/json" \
  -d '{
    "model": "zai-org/GLM-5.2",
    "messages": [{"role": "user", "content": "hello"}]
  }'

Python, official OpenAI client:

from openai import OpenAI

client = OpenAI(base_url="https://universalvoronez.tech/openai/v1", api_key="nrg-...")

client.chat.completions.create(
    model="zai-org/GLM-5.2",
    messages=[{"role": "user", "content": "hello"}],
)

opencode, in ~/.config/opencode/opencode.jsonc:

{
  "provider": {
    "nergate": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Nergate",
      "options": {
        "baseURL": "https://universalvoronez.tech/openai/v1",
        "apiKey": "{env:NERGATE_API_KEY}"
      },
      "models": {
        "zai-org/GLM-5.2": { "name": "GLM 5.2" }
      }
    }
  },
  "agent": {
    "nergate-max": { "options": { "reasoningEffort": "max" } }
  }
}

Anthropic SDKs need only the base — they append the rest:

from anthropic import Anthropic

client = Anthropic(base_url="https://universalvoronez.tech/anthropic", api_key="nrg-...")

client.messages.create(
    model="zai-org/GLM-5.2",
    max_tokens=256,
    messages=[{"role": "user", "content": "hello"}],
)

Inference

POST/openai/v1/chat/completionsChat completion, streaming supported
GET/openai/v1/modelsModels you may call, with your rates
POST/anthropic/v1/messagesSame pool, Anthropic wire format
POST/anthropic/v1/messages/count_tokensToken count for a request
GET/anthropic/v1/modelsModels in Anthropic shape
GET/api/pricingPublic rate board, no key needed
GET/api/statusOperational or degraded, model count
GET/api/healthPool, catalog and uptime

Bodies are forwarded as they came, apart from what is described below, so provider parameters work as documented upstream — stream, tools, reasoning_effort, temperature, max_tokens.

Streaming is a normal SSE stream. Nergate adds stream_options.include_usage so the final chunk carries token counts, which is also how your spend gets metered.

System prompts

Three ways to set one. Each overrides the one before it.

On the key

Applies to every call made with that key. Set it in the console, or over the API:

curl -X PATCH https://universalvoronez.tech/api/me/keys/<key-id> \
  -H "content-type: application/json" -b cookies.txt \
  -d '{
    "systemPrompt": "You are a terse code reviewer.",
    "systemPromptMode": "replace",
    "defaultModel": "zai-org/GLM-5.2"
  }'
prependYour prompt goes first, the caller's own system message is kept.
replaceThe caller's system and developer messages are dropped. Useful against clients that inject a large prompt of their own.

Per call, with headers

X-Nergate-System-Prompt: Answer in one sentence.
X-Nergate-System-Mode: replace

In the body

A plain {"role": "system"} message works as usual, and is what the two settings above act on.

Reasoning effort

Nothing is applied unless you ask for it: a model you call plain gets no reasoning_effort at all, and behaves as its own default. To pick a level, append it to the model id — the gateway strips the suffix and passes the effort up:

zai-org/GLM-5.2          # nothing added
zai-org/GLM-5.2-low      # fast and cheap
zai-org/GLM-5.2-max      # thinks hardest

Not every family accepts every level. GPT-OSS takes only low/medium/high, while GLM, DeepSeek and Kimi also take xhigh and max. Asking for a level a model does not support is a 400 unsupported_effort listing what it does accept — the gateway never silently substitutes another level. GET /openai/v1/models lists the variants each model has.

Four places can set it, later wins:

1key defaultSet on the key in the console, applies to every call
2reasoning_effortThe usual field in the request body
3X-Nergate-EffortHeader, for clients that will not send the field
4-max suffixOn the model id, wins over everything else

In opencode the level is an agent, not a model: the installer writes each model once and adds nergate-low…nergate-max alongside. Switch with opencode --agent nergate-max — the agent's effort applies to whichever model is selected.

X-Nergate-Effort: max

Models without reasoning — Llama, Gemma, Granite, Phi, Mellum — ignore all of this and have no variants.

Account and keys

These use a session cookie from /api/auth/login, not your API key — they are what the console itself calls.

POST/api/auth/loginSign in, sets the session cookie
POST/api/auth/logoutSign out
GET/api/meQuota, spend, usage by model, your keys
POST/api/me/keysIssue a key — the secret is returned once
PATCH/api/me/keys/:idSet system prompt, mode, default model
DELETE/api/me/keys/:idRevoke a key

Catalog and rates

GET /api/pricing is public and lists every model with your price per 1M tokens and the upstream list price. GET /openai/v1/models needs a key and returns the same rates in OpenAI shape, filtered to what your key may call.

Billing is prompt_tokens x input rate + completion_tokens x output rate, charged against your quota. Spend and remaining balance are on the console, and the full board is on the pricing page — 26 models.

Errors

Errors come back in OpenAI shape: {"error": {"message", "type", "code"}}.

401invalid_api_keyKey missing, unknown or revoked
402quota_exceededYour credit quota is spent
403model_not_allowedThis key may not call that model
400unsupported_effortModel does not take that effort level
400context_length_exceededmax_tokens is larger than the model window
400unsupported_contentImages sent to a model without vision
429rate_limitToo many calls this minute
502pool_exhaustedEvery upstream attempt failed — retry
503no_accountsNo capacity right now — retry shortly

A failing upstream account is retried on another one automatically, so pool_exhausted means several accounts failed in a row for the same call.

Nergate — operator-run AI gateway panel rev. 4