This page is for the admin. It explains how the gateway decides which real provider answers a request, and how to make several providers work as one. Everything here is set in the admin UI at https://llm.hjc925.cloud/ui, under Models. The gateway stores all of this in its database. There is no config file to edit. Each section below has an interactive diagram. Drag to pan, scroll to zoom, switch theme, and press F for fullscreen.

The two names of every model

Every model entry has two names. They do different jobs. A caller never sees the real upstream. The caller only knows the public name.

The normal path: aggregation and routing

In the standard setup, the gateway serves as a central aggregation hub. Many client tools converge on a single front door, and the gateway routes each request out to the appropriate model provider.

How aggregation works

  1. Fan-in across tools and protocols: Client tools do not talk to individual AI providers directly. Claude Code (using the Anthropic /v1/messages format), coding agents like opencode or Codex (using the OpenAI /v1/chat/completions format), and custom applications all target the same gateway endpoint (https://api.hjc925.cloud/v1).
  2. Central authentication & accounting: The gateway verifies the virtual key, enforces user and team spend limits, and logs token usage before forwarding the request. Provider secrets remain secured on the server and are never exposed to clients.
  3. Fan-out to upstream providers: In this baseline normal path, each requested public model name maps directly to one upstream provider (minimax/…, glm/…, openrouter/…, and the unprefixed names). The gateway translates protocols where necessary and forwards the payload using the provider credentials configured on the server.
This aggregation architecture is the baseline. Once a model is routed through the gateway, advanced capabilities like pooling (several providers behind one public name) and fallbacks (failing over to an alternate model group) operate seamlessly without requiring configuration changes in your client tools.

What a pool is

A pool is two or more model entries that share the same public name but point at different real upstreams. When a request arrives for that public name, the gateway spreads it across the entries in the pool. If one entry fails or is rate-limited, the gateway cools it down and sends the next request to another entry. A pool is easy to recognize in practice: the same public name appears more than once in the model list, with different real upstreams behind each entry. A request to that name can be answered by any of them.

Example: the DeepSeek pool on this gateway

Every member is registered under the same public name. deepseek-v4.1-flash is a four-member pool: The four entries are one pool. The gateway chooses between them on each request, in proportion to the weights — so DeepSeek’s own API at weight 0 is currently never picked, while the two AlibabaCloud regions take 5 and 3 parts of every 10. If one upstream is down, requests still succeed through the others.

What happens if you name them differently

If you register the two entries under different public names, they become two separate pools. A caller who asks for deepseek-direct will only ever reach DeepSeek. A caller who asks for deepseek-via-openrouter will only ever reach OpenRouter. They do not share traffic. This is not a mistake. Separate names are useful when you want to keep two routes apart — for example, a cheap pool and a reliable pool, or a pool for one team and a pool for another. To make them work as one again, give them the same public name.

Wildcard routes

A wildcard route catches many model names under one provider prefix. The route anthropic/* means: any model name that starts with anthropic/ and is not explicitly registered is proxied through the Anthropic key. This is why a caller can use anthropic/claude-opus-4-8 without anyone registering that exact name first. This gateway has exactly two wildcard routes. There is no openrouter/* and no gemini/*, so those prefixes work only for names registered one by one — gemini/gemini-3.8-flash and four openrouter/… entries. Anything else under them is refused with no healthy deployments for this model. Check GET /v1/models before assuming a prefix behaves as a wildcard.
A wildcard route uses one key for every model under that prefix. Make sure the key is allowed to access all of them.
Explicit entries win over wildcards. If anthropic/claude-sonnet-5 is registered exactly, it is used; the wildcard does not intercept it.

Fallbacks

A pool spreads traffic across equivalent routes with the same public name. A fallback is different: when a request to one public name fails, the gateway retries it under a different public name. Example: a request to deepseek-v4.1-flash fails after retries. A fallback rule can send it to minimax/MiniMax-M3 instead.
The gateway tries fallbacks in the order you list them. There are three kinds: Fallbacks are set per model, in the admin UI or through the management API.

How the gateway chooses (routing strategies)

When several entries share a public name, the gateway picks one using a strategy. The default is simple-shuffle: it spreads requests roughly evenly. You can also set a weight on an entry. A weight of 9 against a weight of 1 sends roughly 9 of every 10 requests to the first entry.
simple-shuffle is the sensible default for most gateways: it adds the least delay and spreads load evenly. Change it only when you have a specific reason — for example a cost ceiling or a latency target.

Three rules about weights that are easy to get wrong

Weights look simple and behave in three ways that surprise people. All three come from how simple_shuffle actually reads them, and all three were verified against this gateway rather than inferred from the documentation.
The weighted branch only runs if some entry in the pool carries a weight — and the check is for any entry, not the first one.An earlier version of this page said the first entry had to carry a weight, and that a pool whose first entry was unset would ignore every weight and fall back to uniform random. That was measured on this gateway on 2026-09-24 and it is not true. simple_shuffle builds the weight tuple for every member, takes largest = max(...), and only falls through when that largest value is <= 0. An unset entry simply normalises to 0.0 and is never picked.The measurement: with the first entry unset and the others weighted 1 and 9, 20,000 picks came out first entry 0.00%, weight-1 10.23%, weight-9 89.77% — the weights were honoured, not ignored.
That last row is the one that bites. Setting every weight to 0 does not park a pool — it produces the opposite of what it looks like, because a total weight of zero is treated as “no weighting in force” rather than “send nothing”.
A weight of 0 on a single entry, where other entries have real weights, does work as expected: that entry is never chosen. The trap is only when the total across the pool reaches zero. See When a provider runs out of quota below for what a 0 entry does during an outage — it is a reserve, not a disable.

When a provider runs out of quota

A weight is a plan, not a live reading. It does not fall when a provider starts refusing, and the gateway does not rewrite it. What happens instead is a cooldown: the failing entry is taken out of rotation for a period, and the remaining entries absorb its share in proportion to their own weights. The length of that cooldown is decided in this order:
  1. A cooldown_time set on the entry itself, if there is one.
  2. Otherwise, the retry-after header the provider sent with its 429.
  3. Otherwise, the router’s default of 60 seconds.
Step 2 is the one that matters for quota exhaustion. A provider that has burned a monthly allowance will say so — retry-after on this gateway has come back as large as 309,788 seconds, about 3.6 days. Honouring that header means the entry is set aside until the quota actually resets, instead of being retried every minute for three days and failing every time.

What an entry at weight 0 does when the others cool down

A weight of 0 looks like “never use this”, and while every weighted entry is healthy that is exactly what it does. It is not a disable, though: it is a reserve. Measured on this gateway (2026-09-24): So a parked entry is a last line of defence, not a spare part. The one thing to avoid is putting every entry at 0 — a pool whose total weight is 0 is read as “no weighting in force” and goes uniform, including the entries set to 0.
This only works if the provider integration raises a typed rate-limit error carrying the provider’s headers. A custom provider that lets a raw HTTP error escape gives the router no status code to read, so no cooldown fires at all and the dead entry keeps taking its share of traffic. If you write a custom provider, see Custom providers — this is the single most common way a pool keeps failing after a provider goes down.

Seeing it happen

The Companion pool status page shows, per pool: every member, the weight it carries, the share that weight actually buys it, and which members failed their last health check. Loading it costs nothing — it reads the gateway’s cached health. The Check now button runs a live check, which really calls every member, so it is deliberately a button and not something the page does on a timer. One limit worth knowing: the gateway exposes no list of what it is currently cooling down. The page is honest about that — it reports what failed its last health check, which is the closest signal available, and says so rather than implying it knows the router’s mind.

How to set it up, step by step

1

Open the admin UI

Go to https://llm.hjc925.cloud/ui and sign in.
2

Open Models

The Models page lists every registered model and wildcard route.
3

Add a provider key first

Add the provider and its API key. The key is stored encrypted on the server. It is never shown again after you save it.
4

Add a model entry

Give it the public name you want callers to use.
5

To make a pool, add the same public name again

Add a second entry with the same public name but a different provider or key. The gateway treats them as one pool.
6

To use wildcards, add a route with an asterisk

For example anthropic/*, backed by the Anthropic key.
7

Test it

Make a request through https://api.hjc925.cloud/v1 using the public name.

Common questions

Check that the public names are exactly equal, including any provider prefix. deepseek-v4.1-flash and deepseek/deepseek-v4.1-flash are different public names, and only the first exists. The gateway matches the full string.
No. The caller only knows the public name. The gateway decides. That is the point of a pool.
The gateway returns an error after its retries are exhausted. A fallback rule to another model group would then catch the request.
No. Model changes in the admin UI apply immediately. The gateway reloads from the database.