Key concepts
A definition-list map of every primitive you will meet in the Ringside API, from the developer key and customer through wallets, model references, vector stores, assistants, webhooks and batches. Each one gets a precise sentence or two and a link to its deep page. Read it once and the rest of the docs slot into place.
Ringside is a metering and routing layer in front of 19 LLM providers. You hold one developer account; you mint API keys from it; every call you make flows through the metering layer, gets priced, and draws down a wallet. Most of the surface is OpenAI wire-compatible, so the SDK you already use keeps working. The concepts below are the parts that are Ringside's own: the ones that turn a raw provider proxy into a billable multi-tenant platform you can resell.
Read this page as a territory map. Each term is defined precisely, with the real id prefix, header name, default or limit, and a link to the concept or reference page that covers it in full. Where a primitive is also an HTTP resource, the link points at its endpoint reference under /docs/<endpoint>.
The API base is https://api.fightclub.pro/v1. The dashboard, playground and these docs live at https://ringside.fightclub.pro. The only versioning is the /v1 path segment. There is no version header.
Identity and access
Two kinds of credential reach the API, and the Authorization scheme is what tells them apart. A developer key is your root credential and uses Bearer. A client token is a short-lived, browser-safe credential scoped to a single customer and uses the Client scheme. Anything else is rejected with a 401 whose message reads "Authorization header must start with 'Bearer ' or 'Client '".
- Developer account
- Your tenant. It owns every key, customer, wallet, vector store, webhook and assistant you create. All resources are private to the account; cross-account access returns 404, never 403, so ids cannot be probed for existence. See Authentication.
- API key
- Your root credential, literally
ko_followed by 64 hex characters. Sent asAuthorization: Bearer ko_<key>, SHA-256 hashed at rest. There is no test-vs-live distinction. Create and revoke keys in the dashboard at/app/api-keys. Keep it server-side only. See Authentication. - Client token
- A short-lived JWT (jti prefix
ctok_) minted server-side via POST /v1/customers/{id}/client_tokens so a browser can call the API without ever shipping your key. The browser sendsAuthorization: Client <jwt>(theClientscheme, notBearer). Default ttl 900s (min 60, max 3600), default rpm 60 (max 600, clamped to the customer rpm). Minting needs a key with theapi:client_tokensscope and is rate-limited to 100/min per customer. See Authentication.
# Developer key (server-side): Bearer scheme
curl https://api.fightclub.pro/v1/chat/completions \
-H "Authorization: Bearer ko_<64-hex>" \
-d '{"model":"fc:openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'
# Client token (browser): Client scheme, scoped to one customer
curl https://api.fightclub.pro/v1/chat/completions \
-H "Authorization: Client <jwt>" \
-H "Origin: https://app.example.com" \
-d '{"model":"fc:openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'Customers and spend
A customer is the first-class end-user primitive. It is how you attribute spend, enforce a budget and scope a client token. If you resell access to your own users, each one becomes a customer, and Ringside tracks their cost, optional markup and margin for you. Customers are cheap and optional. A bare metered call with no customer attribution just bills your developer wallet.
- Customer
- An end-user you provision (id prefix
cus_). Carriesexternal_id(your own id, addressable later asext:<external_id>on every customer subroute),display_name,monthly_budget_usd,rpm_limit,tpm_limitand metadata up to 4 KB. Over budget returns 402customer_budget_exceeded. See The customer and spend model and the Customers reference. - Provisioned account
- A full, isolated Ringside account you create for a customer via POST /v1/accounts using your
rsk_developer provisioning key. Unlike a customer, it has its own login, vault, ko_ key and (optionally) Brain. Itsowner_typeisapi_end_userand itsidis a UUID, not a prefixed id. Distinct from acus_customer, which is a billing sub-entity with no login. See Provision accounts for your customers. - FC-Customer header
- Attribute a metered call to a customer by setting
FC-Customerto thecus_id, anext:<external_id>, or a plain external id string. Unknown values lazy-create the customer unless you also sendFC-Customer-Strict: true. See The customer and spend model. - Developer wallet
- Your prepaid balance. Every call draws from it unless the call is attributed to a customer that carries its own wallet. An empty developer wallet returns 402
wallet_empty. See Metering and billing. - Customer wallet
- An optional prepaid balance per customer, topped up via POST .../wallet/topup and corrected via
.../wallet/adjust. When a call is attributed to a customer that has a wallet, the charge lands there instead of your developer wallet; an empty one returns 402customer_wallet_empty. See The customer and spend model. - Budget
- A customer's
monthly_budget_usdceiling. It is independent of the wallet: a budget block (402customer_budget_exceeded) can fire while your wallet still holds money, because the budget is a spend cap, not a balance. See The customer and spend model.
Metering, margin and billable amount
Every billable call is priced and the charge is reported back on the response. Two headers carry the numbers. X-Ringside-Wallet-Balance is the balance after the call and X-Ringside-Wallet-Deducted is the exact amount this call cost. If you resell, you set what the customer pays, separate from what the call actually cost you, and the gap is your margin.
- Billable amount
- What a customer is billed for a call, which can differ from raw provider cost. Set a default per customer with
default_billable_markup_pctordefault_billable_amount_usd(mutually exclusive; setting both returns 400billable_defaults_mutex), or override one call with theX-FC-Billable-Amountrequest header in micro-dollars. See Metering and billing. - Margin
- Billable amount minus raw cost, accumulated per customer. Read it at GET /v1/customers/{id}/margin. This is the number you watch when you resell at a markup. See Metering and billing.
- Request id
- A unique id per request, prefix
req_, returned on theX-Request-Idresponse header. It is a header, never a body field. Quote it in support tickets and log it on every call. See Observability. - Idempotency key
- A header, name exactly
idempotency-key(lowercase), that de-dupes retries within a 24 h replay window, scoped to (developer, customer, key). Combining it with amodel:[]fallback cascade returns 400fallback_with_idempotency_unsupported. See Idempotency and reliability.
Every error body is { "error": { "type", "code", "message", "param"?, "retry_after_seconds"? } }. type, code and message are always present; param appears on field-validation errors and retry_after_seconds on rate-limit errors. Always branch your code on code, never on the human-readable message. See the Error catalog.
Model references
You never pass a bare model name. Every model ref carries a prefix that tells Ringside how to resolve it, and a bare name like gpt-4o is rejected with 400 invalid_model_ref. The prefix is the difference between pinning an exact model forever and asking for "the current best Sonnet". Pick the one that matches how tightly you want to control the target.
| Prefix | Form | Resolution |
|---|---|---|
fc: | fc:openai/gpt-4o-mini | Pinned. Exactly this provider and model, no resolution. |
match: | match:anthropic/sonnet>=4 | Resolves a constraint to a concrete fc: model at call time. |
slot: | slot:fast | A dev-defined alias you point at a model and can re-point without code changes. |
dyn: | dyn:cheap-coding | A dynamic profile that picks a model per call; the choice is reported on X-Ringside-Dynamic-Profile. |
- Model reference
- A prefixed string identifying which model to run. The concrete
fc:target amatch:/slot:/dyn:ref resolved to is returned on theX-Ringside-Model-Resolvedresponse header. See Model references and resolution. - Region suffix
- A
@<region>suffix onfc:refs only, pinning where inference runs for data residency:@eu/@us(OpenAI in-region),@eu-bedrock/@us-bedrock(Claude via AWS Bedrock) and@eu-vertex/@us-vertex(Claude via Google Vertex). An unknown suffix returns 400. See Regions and data residency. - Fallback cascade
- Pass
modelas an array (e.g.["fc:openai/gpt-4o-mini", "fc:openai/gpt-4o"]) and Ringside tries each in turn, escalating on a 5xx or schema-validation failure. The winner comes back onX-Ringside-Model-Used. A cascade cannot combine withstream:true(400fallback_with_stream_unsupported) or an idempotency key. See Add model fallback.
Retrieval: vector stores and RAG
A vector store is a managed RAG index. You create it, attach files, wait for ingest, then query it through the OpenAI file_search tool inside an assistants run. Embedding happens for you. The store is per-customer isolated, optionally encrypted at rest and optionally backed by a knowledge graph. You do not call a query endpoint directly; the store is attached to a run and the model retrieves from it.
- Vector store
- A managed embedding index (id prefix
vs_) created with anembedding_modelfixed at create time (change it via.../migrate). Files ingest asynchronously throughpending | in_progress | completed | failed | cancelled. Query viatool_resources.file_search.vector_store_idsinside a run. See Retrieval-augmented generation and the Vector Stores reference. - GraphRAG
- Set
graphrag_enabled: trueand ingest also builds a knowledge graph;file_searchthen returns afacts[]array of{subject, predicate, object}relationships next to the chunk citations, catching answers spread across documents. Bills as a per-GB-day line, first 1 GB-day per store per day free. See GraphRAG. - Sealed store
- A store with
encryptionset tomanagedorbyok, keeping chunk text and (withvector_sealing: full, the default when sealed) vectors encrypted at rest. Abyokstore needs a passphrase key (>=12 chars) that is never persisted; you take a lease via.../unlockor theFC-Vector-Store-Keyheader, and the store idle-locks otherwise. See Sealed RAG and encryption.
Stateful execution: assistants, threads and runs
The assistants surface is the OpenAI-compatible way to run a stateful agent. The three primitives split cleanly. An assistant is reusable config and holds no conversation state. A thread holds the message history. A run executes the assistant against a thread. When the assistant calls one of your function tools the run pauses and waits for you to feed the result back.
- Assistant
- Reusable config (id prefix
asst_): model, instructions, tools (file_searchor function),tool_resources, sampling and metadata. Itsnameis unique per developer (409assistant_name_in_use). It carries no conversation state. See Assistants, threads and runs and the Assistants reference. - Thread
- A message container (id prefix
thread_), capped at 5,000 messages. Setmetadata.customer_idormetadata.customer_external_idto attribute the thread's spend to a customer. See the Threads reference. - Run
- Executes an assistant on a thread (id prefix
run_), moving throughqueued | in_progress | requires_action | cancelling | cancelled | completed | failed | expired. When the assistant calls a function tool the run pauses atrequires_action; you answer withPOST .../submit_tool_outputs. Only one active run per thread (else 409thread_locked). See the Runs reference.
Events and bulk work
- Webhook
- An HTTPS endpoint (id prefix
wh_) Ringside POSTs signed events to: customer lifecycle, budget and rate-limit blocks,wallet.low/wallet.empty, moderation flags, batch lifecycle and more. The signing secret (whsec_) is returned once on create and on rotate. Each delivery carriesX-FC-Signature: t=<unix>,v1=<hex hmac_sha256>; verify constant-time. A sustained failure streak auto-disables the endpoint. See Webhooks and events and the Webhooks reference. - Batch
- An async bulk job (id prefix
batch_) at half the synchronous per-call price. Submit a file uploaded withpurpose=batchagainst one endpoint (/v1/chat/completions,/v1/embeddingsor/v1/moderations), poll its status, then downloadoutput_file_id(anderror_file_idfor failed lines). Max 50,000 requests per batch; only a24hcompletion window. See Batch processing and the Batches reference. - File
- A stored object (id prefix
file_) used as batch input (purpose=batch), a RAG attachment (purpose=attachments) or a vision input (purpose=vision). Upload is multipart, max 512 MB per file, Bearer key only (a client token gets 403). See the Files reference.
Cross-cutting mechanics
A few rules apply everywhere, not to one resource. Knowing them up front saves you debugging the same surprise on three different endpoints.
- Pagination
- List endpoints return
{ "data": [...], "has_more": boolean, "next_cursor": string | null }. There is no"object": "list"field. Page forward by passing the previousnext_cursorasafter;limitdefaults to 20 (max 100 on most lists; vector-store queries default 50, max 200). See Limits and quotas. - Rate limits
- Four enforcement layers, lowest wins: edge per-IP, per-client-token, per-developer and per-customer. The response carries
X-RateLimit-Remaining; a 429 addsRetry-Afterin integer seconds. There is noX-RateLimit-LimitorX-RateLimit-Reset. See Rate limiting and quotas. - Tenant isolation
- Every resource is private to your developer account, and accessing one that belongs to another tenant returns 404 (
not_found_error), never 403. Ids are therefore not enumerable. See Security.
| Prefix | Resource |
|---|---|
cus_ | Customer |
req_ | Request id |
ctok_ | Client token (jti) |
asst_ | Assistant |
thread_ | Thread |
run_ | Run |
vs_ | Vector store |
file_ | File |
batch_ | Batch |
conv_ | Conversation |
wh_ | Webhook |
chatcmpl- | Chat completion |
whsec_ | Webhook signing secret |
A worked example
Here is most of the map in one call: a chat completion attributed to customer cus_42, with a fallback cascade, reading the metering and resolution headers back off the response. The request is plain OpenAI wire shape plus Ringside headers.
curl -i https://api.fightclub.pro/v1/chat/completions \
-H "Authorization: Bearer ko_<64-hex>" \
-H "FC-Customer: cus_42" \
-H "Content-Type: application/json" \
-d '{
"model": ["fc:openai/gpt-4o-mini", "fc:openai/gpt-4o"],
"messages": [{"role":"user","content":"Summarize our refund policy."}]
}'
# Response headers worth reading:
# X-Request-Id: req_8f2c...
# X-Ringside-Model-Used: fc:openai/gpt-4o
# X-Ringside-Models-Tried: fc:openai/gpt-4o-mini,fc:openai/gpt-4o
# X-Ringside-Fallback-Triggered: true
# X-Ringside-Wallet-Deducted: 0.000412
# X-RateLimit-Remaining: 59If cus_42 is over budget you get 402 customer_budget_exceeded even with a funded developer wallet. If your developer wallet is empty it is 402 wallet_empty. Add stream: true to the cascade above and it is rejected 400 fallback_with_stream_unsupported. Add an idempotency-key to the cascade and it is rejected 400 fallback_with_idempotency_unsupported. Branch on error.code in every case.