The Docs.
Get up and running in 5 minutes. Drop in an OpenAI SDK base URL, attach a Customer header and you're live.
Quickstart
One curl. Drop-in SDK call. Then add the FC-Customer header to start attributing spend.
curl https://api.fightclub.pro/v1/chat/completions \
-H "Authorization: Bearer $FC_API_KEY" \
-H "FC-Customer: cus_42" \
-d '{"model":"fc:openai/gpt-4o-mini","messages":[{"role":"user","content":"Hello"}]}'Get your API key in the dashboard.
Authentication
Two auth modes. Pick based on where the call originates.
- API keys (server-side), long-lived
sk_live_*. Use anywhere you can keep a secret. - Client Tokens (browser), short-lived JWTs (15 min default, 1 h max) scoped to one Customer + Origin + IP. Mint on your server via
POST /v1/customers/:id/client_tokens, ship to the browser, browser calls Ringside directly withAuthorization: Client <jwt>.
Customers
Customers are first-class. Think of them as the Stripe-Customer-for-AI.
- Create one per end-user / tenant in your app
- Attach monthly budget cap + per-request rate limits (RPM / TPM)
- Pass the
FC-Customerheader on every call to attribute spend - Receive
budget.exceeded+wallet.lowwebhooks
Prepaid customer wallet
Customers can also carry a prepaid wallet balance, useful for credit-based products, chatbots, or any app where end-users pay in advance.
POST /v1/customers/:id/wallet/topup, add credits to a customerGET /v1/customers/:id/wallet, live balance + full transaction history- Each LLM call automatically debits
billable_amount_usdfrom the wallet and returnsX-Ringside-Wallet-Balance/X-Ringside-Wallet-Deductedresponse headers - Requests are blocked with
402 customer_wallet_emptywhen the balance reaches zero - Transaction types:
topup,debit,adjust,refund
Models
19+ providers in one namespace. Every model ref needs a prefix: fc:<provider>/<model> for a direct model (e.g. fc:openai/gpt-4o-mini), slot:<alias> for a dev-defined alias, or match:<selector> for a declarative pick. A bare name like gpt-4o returns 400 invalid_model_ref. Live pricing on the pricing page.
Streaming
Set stream: true to receive Server-Sent Events. Format matches OpenAI byte-for-byte. Cancel mid-stream by aborting the request. No reconnect/resume in v1.
Errors
Every error returns a JSON envelope:
{
"error": {
"type": "invalid_request_error",
"code": "budget_exceeded",
"message": "Customer cus_42 has exhausted its monthly budget.",
"request_id": "req_abc",
"retry_after_seconds": null
}
}Common codes: invalid_api_key (401), customer_budget_exceeded (402), customer_wallet_empty (402), wallet_empty (402), customer_rate_limit_exceeded (429), model_unavailable (503), invalid_request (400), conversation_locked (409).
Rate limits
Four layers, applied in order. The lowest limit hit wins.
- Edge / per-IP, drive-by abuse
- Per-Client-Token, browser tokens
- Per-developer (you), your account
- Per-Customer, your end-user's setting
Every response includes X-RateLimit-Remaining and on 429 a Retry-After header.
Webhooks (TL;DR)
Register a URL, receive HMAC-signed events. Verify the X-FC-Signature header.
Full guide → /docs/webhooks
SDKs
Two ways to call Ringside. Pick whichever fits your stack.
1. Use the OpenAI SDK (drop-in)
Wire-compatible. Swap the base URL and your existing OpenAI client keeps working — chat, embeddings, images, audio, files, batch, assistants.
from openai import OpenAI client = OpenAI(base_url="https://api.fightclub.pro/v1", api_key=os.environ["FC_API_KEY"])
2. Use the official Ringside SDK
Thin layer on top — adds typed helpers for the Ringside-only primitives: match: model selector (rolls forward across model versions automatically), per-end-customer CRUD against /v1/customers, webhook signature verification, Client Tokens. The OpenAI surface stays the same; you call client.chat.completions.create(...) exactly like before.
Available in four languages:
pip install ringside(Python)npm install @ringside/sdk(TypeScript / Node)go get github.com/fightclub/ringside-go(Go)pro.fightclub:ringside-sdk:0.1.0(Java 17+, Maven)
Same shape across every SDK — newest Sonnet at major version ≥ 4, resolved at request time:
from ringside import Ringside, Model
client = Ringside(api_key=os.environ["FC_API_KEY"])
model = Model.match("anthropic", "sonnet").min_version(4).build()
# -> "match:anthropic/sonnet>=4"
reply = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": "Hello"}],
)
# Inspect what actually served (X-Ringside-Model-Resolved header):
# raw = client.chat.completions.with_raw_response.create(...)
# raw.http_response.headers["x-ringside-model-resolved"]
All four SDKs also ship client.customers (CRUD), client.webhooks.verify() (HMAC-SHA256 with replay protection), and the same Model.match() / Model.fc() builders. Source + examples: github.com/fightclub. Cookbook recipes at /examples.
Prompt caching
On Anthropic-routed models, repeated prompt prefixes can be cached so you don't re-pay full input price every call. Cached reads bill at 10% of the input rate, the cache write costs 1.25x once, and the savings land on your bill (see pricing). Two ways to turn it on.
Explicit breakpoint. Put cache_control on the message whose content (plus everything before it) should be cached. Tools and the system prompt render before the messages, so a breakpoint on the system message caches the tool manifest too.
Auto. Set "prompt_cache": "auto" and Ringside places the breakpoint on your last system message for you. Default is off. Non-Anthropic upstreams ignore both controls without error.
curl https://api.fightclub.pro/v1/chat/completions \
-H "Authorization: Bearer $FC_API_KEY" \
-H "FC-Customer: cus_42" \
-d '{
"model": "fc:anthropic/claude-sonnet-4.6",
"prompt_cache": "auto",
"messages": [
{"role": "system", "content": "<long stable system prompt + tool manifest>",
"cache_control": {"type": "ephemeral"}},
{"role": "user", "content": "First question"}
]
}'When caching happens, the usage object carries the cache breakdown alongside the usual three fields (and the same fields ride the terminal usage chunk on streaming calls when stream_options.include_usage is set):
"usage": {
"prompt_tokens": 38120,
"completion_tokens": 412,
"total_tokens": 38532,
"cache_creation_input_tokens": 36900,
"cache_read_input_tokens": 0,
"prompt_tokens_cached": 36900,
"prompt_tokens_details": { "cached_tokens": 0 }
}First call writes the cache (cache_creation_input_tokens > 0); a follow-up with the same prefix within the cache window reads it (cache_read_input_tokens > 0). The cacheable prefix has a minimum size of roughly 4,000 tokens on current Opus and Sonnet models. Shorter prefixes won't cache, and both cache fields stay at 0.
Vector stores (managed RAG)
Vector stores ingest customer files, parse them into searchable chunks, embed and index them inside a per-customer tenant. Query via the OpenAI file_search tool inside any Assistants run.
# 1. Create a store
curl https://api.fightclub.pro/v1/vector_stores \
-H "Authorization: Bearer $FC_API_KEY" \
-d '{"name":"acme-handbook","embedding_model":"text-embedding-3-small"}'
# 2. Upload + attach a file
curl https://api.fightclub.pro/v1/files \
-H "Authorization: Bearer $FC_API_KEY" \
-F purpose=attachments -F file=@handbook.pdf
# => { "id": "file_xyz" }
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/files \
-H "Authorization: Bearer $FC_API_KEY" \
-d '{"file_id":"file_xyz"}'
# 3. Query inside an Assistants run
curl https://api.fightclub.pro/v1/threads/$THREAD/runs \
-H "Authorization: Bearer $FC_API_KEY" \
-d '{
"assistant_id":"asst_...",
"tools":[{"type":"file_search","file_search":{"vector_store_ids":["vs_abc"]}}]
}'File ingest is asynchronous. vector_stores.files.create returns immediately with status='pending'. Poll the file status or subscribe to the vector_store.file.completed webhook.
Open MIME surface in v1: PDF, Word, slides, Markdown, HTML, plain text, code, JSON/YAML/XML, images with text (routed through a vision-capable model), audio (transcribed), CSV tables (parsed into row-narratives). You pay parse tokens for what the parser actually ran.
Embedding model is picked at store-create time, switchable later. The dashboard runs a background re-embed against cached parses and atomically swaps the index when done. The old index sticks around for 7 days for rollback. You pay embedding tokens only.
Per-customer isolation by default. Cross-customer reads return 404, not 403.
The browse/poll/manage surface is OpenAI-compatible. Common follow-ups:
# List stores
curl https://api.fightclub.pro/v1/vector_stores \
-H "Authorization: Bearer $FC_API_KEY"
# List + poll files in one
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/files \
-H "Authorization: Bearer $FC_API_KEY"
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/files/file_xyz \
-H "Authorization: Bearer $FC_API_KEY"
# Batch-attach up to 500 file_ids in one call
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/file_batches \
-H "Authorization: Bearer $FC_API_KEY" \
-d '{"file_ids":["file_1","file_2","file_3"]}'
# Query log + aggregated stats (window=24h|7d|30d)
curl "https://api.fightclub.pro/v1/vector_stores/vs_abc/queries?limit=50" \
-H "Authorization: Bearer $FC_API_KEY"
curl "https://api.fightclub.pro/v1/vector_stores/vs_abc/stats?window=7d" \
-H "Authorization: Bearer $FC_API_KEY"
# Switch embedding model (background re-embed, atomic swap, 7-day rollback)
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/migrate \
-H "Authorization: Bearer $FC_API_KEY" \
-d '{"embedding_model":"text-embedding-3-large"}'Full surface (PATCH, DELETE, file detach, cancel/retry on stuck ingests, batch retrieve/cancel, migrations list and rollback): /docs/vector_stores.
Deeper: /rag for the product page, /tutorials/managed-rag-quickstart for the 15-minute walkthrough, /examples/file-search-citations for the citation-parsing recipe.