TierMux Checking…
TierMux Gateway · launching soon

Your keys. Your quota. Your infrastructure.

A self-hostable LLM gateway that speaks OpenAI, Responses and Anthropic over one endpoint, routes across many providers with key rotation and automatic fallback, and runs on your own Cloudflare account — or your own hardware. TierMux maintains the catalog; inference stays entirely on your infrastructure.

your deployment — what crosses the boundary
everything that leaves — two GETs, both public
your gateway TierMux
GET /providers + /models — the catalog
GET /api/service-status — the kill switch
Same cadence, no key, no body, nothing about you in either.
never leaves — by design, not configuration
  • prompts · responses · tool calls
  • provider keys · issued API keys
  • routing state · usage counters
Your gateway counts tokens and keeps a request log — in your database, for your panel. None of it is sent anywhere, and nothing announces that your deployment exists.
Workers + D1 or Docker / Node + SQLite 63 KB gzipped worker No Redis, no Postgres, no migration step MIT licensed Zero telemetry
API surface

One endpoint, every protocol

Whatever shape your client speaks, the same URL answers it — and the two translations happen inside the gateway, so a provider that only talks chat-completions still serves Claude Code and Codex CLI.

POST/v1/chat/completionsOpenAI protocol, streaming and tool loops included.
POST/v1/messagesAnthropic protocol — what Claude Code speaks. /v1/messages/count_tokens too.
POST/v1/responsesThe Responses protocol — Codex CLI and the OpenAI Agents SDK, translated downstream.
GET/v1/modelsListed in auto's current order — the picker shows what the gateway believes is best first.
POST/v1/embeddingsNamed model only — embedding spaces are not interchangeable, so auto is refused here. The catalog carries no embedding model yet, so today this needs one of your own.

Base URL with or without /v1 — both work, and the bare paths (/chat/completions, /messages, /responses, /models) are served too. Which base URL your client wants →

Install

Deploy it

Two targets, one source tree, one schema. The database schema is applied by the app itself on first request — there is no migration command, ever.

Cloudflare Workers + D1 one click

The Deploy button forks the repo into your GitHub, provisions D1, deploys, and wires up Workers Builds. Every later push to your fork auto-deploys.

Needs a browser and a Cloudflare account. Nothing installed locally.

Setup wizard npm run setup

Checks your machine, then either runs the gateway right here or deploys it to Cloudflare for you — create D1, upload the script, enable the workers.dev URL — with real upload progress.

Mac: start.command · Windows: start.bat

Docker / Node + SQLite
docker compose up -d     # :8788/panel

npm install && npm start  # Node 22.5+

# built-in node:sqlite — no
# compile step, no Redis, no
# Postgres. Config: PORT and
# DATA_DIR, nothing else.

The packaged release ships a pre-built Worker bundle — deploying needs no wrangler and no build step on the far end. State survives docker compose down and every image update.

First run

  1. Open /panel. With no admin yet you land on /panel/install.
  2. Set an admin username and password. That is the whole form — you are never asked where the catalog lives, because that is not yours to manage: it loads on its own.
  3. Install locks. /panel/install stops working, so nobody can claim the account after you.
  4. Add a provider key, create an API key. /v1/* refuses to serve until at least one API key exists. No open mode, no default password.
Clients

Connect a client

Pick what you run and follow the steps. The two SDK families do not take the same base URL, and that is the most common way to end up with a working gateway that looks broken.

Claude Code

https://YOUR-DEPLOYMENT — no /v1
  1. Claude Code is configured by environment, not by a settings page. Put these in your shell profile (~/.zshrc), or run them in the terminal you launch claude from.
    Environment shell
    export ANTHROPIC_BASE_URL=https://YOUR-DEPLOYMENT
    export ANTHROPIC_AUTH_TOKEN=tmx_your_key_here
    export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
    export CLAUDE_CODE_ATTRIBUTION_HEADER=0
  2. The base URL has no /v1. Claude Code appends /v1/messages itself, so a URL ending in /v1 sends every call to /v1/v1/messages and 404s — which it then reports as “authentication failed”.
  3. Start it with claude. Model discovery reads /v1/models from the gateway, so /model lists what you actually have.
  4. Verify it. This lists the models your gateway will actually serve — a 200 with a non-empty list means the URL, the key and the catalog are all good.
    Verify curl
    curl -s -H "Authorization: Bearer tmx_your_key_here" \
      "https://YOUR-DEPLOYMENT/v1/models"

Every deployment shows these same steps with your own URL and a real key already filled in, under Connect in the panel.

Routing

How a model is chosen

A fixed, deterministic order — and a substitution is always announced, never silent.

  1. Exact alias — yours, panel-configured, resolves before model names
  2. Exact model_id
  3. Normalized key — deepseek/deepseek-v4:free and deepseek-v4 collide, which is what makes cross-provider fallback possible
  4. Wildcard match — deepseek/* style patterns
  5. Configured fallback alias
  6. auto — unknown model names land here instead of dead-ending on a 404. The substitution is never hidden: x-tiermux-model and the response body both name what actually ran

Before any of it: requests declaring tools only reach tool-capable models, image-carrying requests only vision-capable ones, and prompts are steered away from models whose context window cannot hold them.

The auto model

Ranks the catalog on what the gateway can observe: the shared catalog's prior (quality tier, free, tools, recommended, reasoning, price, context) combined with this install's own measurements (circuit state, per-model health, remaining budget, observed latency). The tier is the catalog's own band — frontier, strong, mid, small — the same tag the VS Code extension routes on, and a request carrying tools is never led by a small or unjudged model. The request itself selects a profile:

autoreads the request auto:codingtools & context first auto:reasoningthinking models auto:fastlowest time-to-first-token auto:freefree only auto:qualityrecommended first

A band name is a profile too — ask for a class of model without learning a single model id:

auto:frontiertop of the measured field auto:strongnear-frontier auto:midsolid and cheap auto:smallutility work
Reliability

When an upstream fails

Each result gets a distinct, deterministic response — and the difference between "retry this" and "this is your fault" is decided once, here, not per client.

Upstream resultVerdictWhat happens
400 · 413 · 415 · 422Caller's faultReturned verbatim, never retried. Asking three providers produces the same rejection three times.
401 · 403Our key is deadThat key parked 15 minutes, request fails over.
402 · 404 · 405 · 501Provider cannot serve itFail over — the model–provider pair is remembered and skipped next time.
408 · 409 · 425TransientFail over.
429Rate limitThat key parked and the next takes over — rate limiting is a property of the credential, not the provider's health. Honours Retry-After (60 s default); a "daily quota gone" body parks the key until midnight UTC. Never opens the circuit.
5xx · 529Provider sickCounts toward two circuits — the provider's (5 consecutive) and that one model's (3). One broken model never takes its provider offline.
Stream dies before first tokenNot a commitmentFalls over. HTTP 200 and headers are not commitment.
Stream dies after first tokenNever falls overThe failure is reported inside the stream — switching mid-answer would duplicate generation and re-run tool calls.

Recovery is deterministic as well: circuit windows escalate from one minute to thirty, half-open admits exactly one probe, spent rpm/rpd budgets are skipped before they cost a 429, and all health state survives a restart — a Workers isolate recycle does not cost five wasted requests re-learning which provider is sick.

Operating it

The panel

One admin, six tabs, no YAML. /panel is where provider keys go in, API keys come out, and the routing decisions the gateway has already made are visible after the fact.

YOUR-DEPLOYMENT/panel — overview
Gateway
Overview Services 12 Models 407 Connect Activity Settings
Requests 24h1,284
Success99.2%
Median TTFB0.9s
Failovers37

Services · health and observed latency

google 3 keys0.7 s
groq 2 keys0.4 s
experientiallabs 1 key · parked 41 s1.8 s
cerebras circuit open · half-open in 2 m—
ollama keyless1.1 s
req_8f21 · auto → google/gemini-3.8-flash · attempt 1 · 200 · 812 ms req_8f22 · auto → experientiallabs/gpt-5.6-luna · attempt 1 · 429 · key 1 parked 60 s req_8f22 · auto → groq/llama-4.1-70b · attempt 2 · 200 · 402 ms
Panel preview. Overview tab, rendered live in your current theme rather than pasted in as an image. Health, key state and the request log are exactly the fields the gateway records — and the log line is the whole log line: no prompt, no response, no headers.
Services tab 2

Add a provider key, name it, and switch it off without deleting it. Several keys per provider, rotated in request order.

Circuit state and 429 parking are shown per key, not per provider.

Connect tab 4

Issue an API key — shown once, only its hash is stored — then copy a ready-made config block for Claude Code, Codex CLI, Cline or the OpenAI SDK.

Your own deployment URL is already filled in.

Settings tab 6

Aliases, the fallback model, hedging, the catalog source (read-only), and Backup: one JSON file out, one JSON file back in.

The admin account is never part of the export.

Ownership

Who owns what

TierMux owns the one thing that goes stale in every self-hosted gateway — the provider and model list. You own everything that touches your traffic.

TierMux catalog

public · this site
  • verified providers & models
  • capability flags, limits, prices
  • public config, no key
→public config
~5 min poll

Your gateway

yours · your account
  • your provider keys, rotated
  • your aliases, your fallbacks
  • your issued API keys
  • your circuits, your usage data
→one endpoint
3 protocols

Your clients

whatever you already use
  • OpenAI SDK · Codex CLI
  • Claude Code
  • Cline · Kilo Code
  • Zed · anything OpenAI-shaped

Adding a provider to the catalog is a data-only change upstream — your gateway picks it up on its next config poll. No code change, no redeploy, no restart.

What is logged, what is not

Not a promise pinned to a config flag. There is no flag.

In the log

  • request id
  • model
  • selected provider
  • key index
  • attempt
  • status
  • latency
  • error code

Never, anywhere

  • prompts
  • responses
  • tool payloads
  • headers
  • provider keys
  • issued API keys
  • heartbeats
  • usage reports
Status

At launch

The public repo lands with a one-click Deploy-to-Cloudflare button. Until then, this catalog already serves the exact configuration payload the gateway consumes — a gateway deployed against it on day one requires no additional setup. Launch updates will be published first on the announcements feed.