Routing
LIVE — everything on this page is in the shipped code and linked to the line that does it.
A FRODO key is an OpenAI-compatible credential. An editor points at one base URL, sends one key, and the gateway decides — per request — whether that call happens and what it is allowed to cost.
editor ──► thering.lol/api/frodo/v1 ──► [ gate ] ──► private workspace ──► modelThe gate is the product. Everything else is a proxy.
#1 · What one request passes through
Drawn from proxyInference, in the order the code runs it.
The order is deliberate: nothing that costs money is touched until everything free has passed.
Three things in that chart are worth saying out loud:
- The quota is charged after the allowlist, not before. A caller asking for a mode we don't serve gets a 403 without spending a request from their day.
- The mode is resolved last, on the way out. The caller names
frodo-frontier; which model answers it is substituted inresolveModelone line before the fetch, so the upstream id exists inside this function and nowhere a caller can reach it. See the model stack. - The 502 is deliberately vague. An upstream error message could leak the proxy URL, and the proxy URL contains the workspace token — see §3.
#What one accepted request can cost
A quota that counts requests only bounds spend if a request has a bounded cost. The body is
forwarded wholesale, so anything not clamped in clampCost is a
caller-controlled multiplier on a wallet none of them are paying for.
| Clamped | To | Why it exists |
|---|---|---|
n · best_of · num_generations |
1 |
They bill per completion. n:100 was a hundredfold multiplier on one quota check. |
max_tokens / max_completion_tokens |
4096 |
Clamped, not rejected — the call still succeeds. |
| Request body | 1 MB → 413 |
Accumulated into function memory unbounded. Refused rather than truncated: a clipped body still parses as valid JSON. |
| Upstream duration | 120s |
No deadline meant a hung generation billed until the platform killed the function. |
| Abandoned streams | reader cancelled | A caller who walks away should stop costing money on the way out. |
All configurable — config.js, values in
env.example.
#2 · How a key comes to exist
The unlock is re-derived from chain state on every mint. A client claiming the milestone has been reached is not evidence of anything, and there is no HTTP override.
| Step | Guards against | Code |
|---|---|---|
| Message must contain the wallet | Signing for someone else's address | unlock.js:50 |
| Issued within 5 minutes | Replaying a captured signature later | unlock.js:52-56 |
| Cap re-read from contracts | A client that simply claims the gate is passed | unlock.js:19-39 |
| Ceilings charged after proof + unlock | A failed attempt burning an honest holder's budget | keys.js:77-97 |
| One live key per wallet | Farming free wallets to multiply a per-key quota | keys.js:104-115 |
| Only the hash is stored | Us being able to read anyone's key, ever | keys.js:55-69 |
The IP bucket is salted. IPv4 is a 32-bit space, so a bare SHA-256 of an address is reversible
by brute force in seconds and would not be anonymous at all
(keys.js:49-53). x-forwarded-for is read from the right:
Vercel appends the real peer, so the left entry is caller-controlled and would let anyone spoof a
fresh bucket (keys.js:35-42).
#3 · The trust boundary
One workspace token funds every FRODO key. It is injected into the upstream URL inside the serverless function and exists nowhere else.
The token is never placed in a response body, a response header, or a log line
(config.js:60-65). The browser never holds it. A frodo_sk_
key is worth exactly its own quota and nothing more — revoking it costs the holder their access
and costs us nothing.
#Storage
| Durable | Upstash Redis over REST, when KV_REST_API_URL + KV_REST_API_TOKEN are set |
| Fallback | A per-instance in-memory Map — not durable, key records do not survive across serverless instances |
| How you tell | GET /v1/models reports store: "durable" or store: "memory" |
Do not open the gate on
memory. Every caller is told which store is in use precisely so this is never a guess — store.js:1-11.
#Next
- Endpoint catalog — every route, the error envelope, and the editors it drops into
- Compute — what is behind the proxy, and what replaces it