The Ring DOCS·Endpoints
MARKET
The Ring · $FRODO
← Ring Marketplace Backrooms SEALED Docs Trade

Endpoint catalog

LIVE — six routes. Two strings is the whole integration:

Base URL   https://thering.lol/api/frodo/v1
API key    frodo_sk_…

No extension, no SDK of ours, nothing to install. If a tool speaks the OpenAI protocol, it speaks this.


#Routes

Route Method Shape Auth OK Fails with
/api/frodo/v1/chat/completions POST OpenAI Authorization: Bearer · x-api-key 200 streamed 400 401 403 405 413 429 502 503
/api/frodo/v1/messages POST Anthropic x-api-key · Authorization: Bearer 200 streamed same
/api/frodo/v1/models GET OpenAI optional — validated when sent 200 401 403 405
/api/frodo/key/create POST wallet signature 201 401 403 429 503
/api/frodo/key/rotate POST key + wallet signature 201 401 403 429 503
/api/frodo/key/revoke POST key + wallet signature 200 401 403 405

Every route answers OPTIONS with 204 and CORS headers allowing authorization, content-type, x-api-key and anthropic-version. Preflight used to hit the method check and get 405, which broke every browser-based client.

#Inference

curl https://thering.lol/api/frodo/v1/chat/completions \
  -H "Authorization: Bearer frodo_sk_…" \
  -H "content-type: application/json" \
  -d '{"model":"<id from /models>","messages":[{"role":"user","content":"say ok"}]}'
bash
curl https://thering.lol/api/frodo/v1/messages \
  -H "x-api-key: frodo_sk_…" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"<id>","max_tokens":64,"messages":[{"role":"user","content":"say ok"}]}'
bash

#Streaming

Set "stream": true and the response is Server-Sent Events, in whichever protocol's shape you asked for. Token-by-token stays token-by-token: the upstream body is piped through with backpressure respected (proxy.js:172-190), not buffered and re-emitted, so the first token reaches you as soon as it exists.

curl -N https://thering.lol/api/frodo/v1/chat/completions \
  -H "Authorization: Bearer frodo_sk_…" \
  -H "content-type: application/json" \
  -d '{"model":"frodo-frontier","stream":true,"messages":[{"role":"user","content":"count to 3"}]}'
bash
data: {"choices":[{"delta":{"content":"1"}}]}
data: {"choices":[{"delta":{"content":", 2"}}]}
data: [DONE]

Hanging up stops the spend. Close the connection and the gateway aborts upstream rather than letting an abandoned generation bill to the end — the same AbortController the 120s deadline uses. That is the one thing worth knowing that most proxies get wrong: a cancelled read on your side has to become a cancelled request on ours, or every timeout you set costs full price anyway.

Errors during a stream arrive as an SSE frame, not as a status code — the status was already sent with the headers. Parse for an error key on each frame if you need to distinguish a truncated answer from a finished one.

#Discovery

curl https://thering.lol/api/frodo/v1/models -H "Authorization: Bearer frodo_sk_…"
bash
{
  "object": "list",
  "data": [{ "id": "frodo-frontier", "object": "model", "created": 1704067200, "owned_by": "frodo" }],
  // outside the OpenAI shape — clients ignore what they don't recognise
  "enabled": true,
  "store": "durable",          // "memory" means the per-instance fallback: not durable
  "max_output_tokens": 4096
}
jsonc

The ids here are modes, never upstream model ids — see the model stack.

Discovery stays open so a client can read the allowlist before it holds a key. But when a key is presented it is verified — several editors validate a pasted credential by calling this route, and answering 200 to a bad key tells the user it works and then fails on the first message.

#Key management

create mints once the unlock is confirmed on chain, and refuses otherwise — there is no HTTP override and there should never be one. See the key lifecycle.

revoke requires the key and a signature from the wallet it was minted to: a leaked key alone cannot lock its owner out, and a wallet alone cannot revoke a key it does not hold.

rotate swaps a key for a fresh one and retires the old one in the same request, so a rotation never leaves two live keys behind. It deliberately does not re-check the unlock — the holder already earned the key, and a market cap that later fell should not strand them (rotate.js:13-19).


#The error envelope

Errors use OpenAI's shape, because that is what every client here parses:

{ "error": { "message": "That model is not available through FRODO.", "type": "permission_error", "code": null } }
json
Status type Means Retry?
400 invalid_request_error Body is not valid JSON no — fix the body
401 authentication_error Missing or unknown key no — fix the key
403 permission_error Key revoked · mode not served · wallet does not own the key no — costs no quota either way
405 invalid_request_error Wrong method no
413 invalid_request_error Body over 1 MB — refused, not truncated no — send less
429 rate_limit_error Quota yes — wait out Retry-After: 60
502 api_error Upstream unavailable. Deliberately opaque: a detailed message could leak the proxy URL yes — exponential backoff
503 api_error Gateway disabled, unconfigured, or at global capacity maybe — capacity clears, a disabled gateway does not

Only the 4xx that mention quota are worth retrying. Everything else in the 4xx column is a statement about your request, and sending it again unchanged produces the same answer while burning whatever budget the retry loop has. Back off on 429, 502 and 503; fix and resend the rest.

A refusal costs you nothing. The allowlist is checked before the quota (proxy.js:118-127), so a 403 for a mode we do not serve does not spend a request from your day. Neither does a 401, a 400 or a 413 — nothing is charged until a request is actually forwarded.

error used to be a bare string. Every client below reads error.message, so all of them rendered undefined and swallowed the reason. Map: proxy.js:14-22.


#What passes through

The gateway forwards your request body wholesale and clamps exactly four things (clampCost). Everything else the chosen model supports reaches it unchanged — so this is a compatibility list, not a feature list we had to build.

Streaming (SSE) LIVE both wire formats
Tool / function calling LIVE forwarded as sent; support is the model's
Structured outputs · JSON mode LIVE same
System messages LIVE untouched — and on frodo-raw, nothing is added to them
Vision · image input LIVE same
Multi-turn, stop sequences, temperature, seed LIVE same
n · best_of clamped to 1 they bill per completion
max_tokens clamped to 4096 clamped, not rejected
Embeddings · images · audio · video not routed this gateway is chat completions

The rule is simple: if the model can do it, your key can do it. The only things that do not arrive as you sent them are the four cost multipliers above, and each is clamped rather than refused, so a request never fails for being ambitious.

#Coming to this surface DESIGNED

Documented here because clients are built against a surface, not against a changelog. Each of these extends something the gateway already computes — see the build for the hook.

x-ratelimit-limit · -remaining · -reset DESIGNED on every response; checkQuota already derives all three
GET /api/frodo/v1/limits DESIGNED read your ceilings without spending a request
usage on every response DESIGNED tokens in, tokens out, and what it cost your day
Mode variants — :fast · :code DESIGNED modes
GET /api/frodo/v1/attestation PLANNED nonce-bound enclave proof — L3

Until a row here says LIVE, the route returns 404 and the header is absent. Nothing on this page is stubbed to look implemented.


#Limits

Default
Requests / minute / key 20
Requests / day / key 500
Max output tokens 4096 — requests above are clamped, not rejected
n · best_of clamped to 1 — they bill per completion
Request body 1 MB → 413
Upstream deadline 120 s
Keys per wallet 1 live — re-minting revokes the previous
Mints / wallet / day · / network / day · global 3 · 5 · 500

Every value is an env var (env.example). They are low because all FRODO keys spend from one funded workspace — see the trust boundary.


#Where the key works

Client How Notes
Cursor Cursor Settings → Models → OpenAI API Key + Override Base URL Tab autocomplete and Ctrl+K stay on Cursor's own backend regardless — that is not your key failing
GithubCopilot VS Code (Copilot BYOK) Chat: Manage Language Models → OpenAI Compatible BYOK covers chat, not inline ghost-text completions
opencode OpenCode opencode.json@ai-sdk/openai-compatible provider Keep name set, or baseURL/apiKey may not be forwarded
Any OpenAI SDK base_url= + api_key= Python, JS, Go — the protocol is the integration
Antigravity not possible No BYOK setting; it talks only to Gemini. The MITM workarounds want the Gemini wire format and carry account-ban warnings

Full setup per editor, with sources and the failure modes worth knowing before you judge it: CLIENTS.md.


#The FRODO model stack

One key, three modes. A caller names a mode. Which model answers it is decided inside the gateway and is never returned, logged, or published.

Mode What it is
frodo-frontier FRONTIER Maximum intelligence — the major labs' top models, routed.
frodo-raw RAW No hidden system prompt. No forced personality.
frodo-private PRIVATE No prompt logs. Zero-retention routing.

These are the only model ids the product publishes. They are not weights of ours and nothing here claims they are: a mode is a routing profile over capacity the key already reaches. The candidate models behind RAW and PRIVATE are listed on the marketplace as a shortlist to benchmark, not as a menu.

curl https://thering.lol/api/frodo/v1/chat/completions \
  -H "Authorization: Bearer frodo_sk_…" \
  -H "content-type: application/json" \
  -d '{"model":"frodo-frontier","messages":[{"role":"user","content":"say ok"}]}'
bash

In OpenCode that is one line:

"models": { "frodo-frontier": { "name": "FRODO Frontier" } }
jsonc

#How the two layers fit

A mode is served only when it is mapped to a model and that model is on the allowlist. A half-configured mode is invisible rather than a 502 waiting for the first caller to find it.

Set by Public?
FRODO_MODE_FRONTIER · _RAW · _PRIVATE the model each mode resolves to no — the value never leaves the function
FRODO_ALLOWED_MODELS exact-string allowlist of upstream ids no/v1/models publishes modes, not this
frodo-frontier · frodo-raw · frodo-private derived from the two above yes — this is the whole public surface

Leaving FRODO_MODE_RAW and FRODO_MODE_PRIVATE unset is exactly how RAW and PRIVATE stay shut until P1 and P2. There is no second switch to forget, and no way to call a mode that is not mapped — it is a 403, and it costs the caller no quota.

The allowlist is still an exact-string gate, not a filter. An id we map that upstream does not have is a 502; an id upstream has that we do not allow is a 403 from us — which is the whole point. Verified on 2026-08-30: openai/gpt-5.5-pro is in the upstream catalog and the gateway still refused it, so this is a real gate rather than a pass-through wearing one.

Resolution: resolveModel · serveableModes · publication: models.js · tests: node scripts/test-gateway.mjs asserts that /v1/models publishes no upstream id.


#Next

  • Routing — what a request passes through
  • Compute — the hardware behind the proxy
  • Ecosystem — who gets a key, and when

Long's launch flow selects one fee receiver. It does not document a guaranteed percentage or automatic holder split. Any compute purchase or holder routing is operated and disclosed separately by this project. Live figures are read from the launch's contracts. Nothing here is investment advice.

$FRODO · Robinhood Chain 4663 · thering.lol