Prefer Swagger UI? Click hereThe same API in the classic OpenAPI explorer.
// get started · 5 min read

Quickstart

Go from a fresh account to a billed chat completion in five steps. You will mint a developer key, make a raw curl call, repeat it through the stock OpenAI SDK by changing one line, attribute the spend to a customer with a single header and then read the call back in the dashboard with its request id and exact cost. Every value below is real: real header names, real id prefixes and the actual response your account returns.

What you are building

Ringside is an OpenAI-compatible gateway in front of 19 model providers. You point your existing OpenAI client at https://api.fightclub.pro/v1, prefix the model name and your code keeps working. What Ringside adds on top is the metering, per-customer attribution and the dashboard ledger. Every call deducts from a prepaid wallet, every call can be tagged to a customer for resale and every call lands in the dashboard logs with a request id you can quote in a support ticket.

The mental model is two layers. The lower layer is the OpenAI surface you already know (/v1/chat/completions, the same request and response JSON). The upper layer is the set of FC-* and X-Ringside-* headers that carry the Ringside-only behaviour. You opt into the upper layer header by header, so a drop-in port costs you nothing and the billing features are there when you reach for them.

API base URL
https://api.fightclub.pro/v1 for every request.
Dashboard
https://ringside.fightclub.pro (keys at /app/api-keys, logs at /app/logs, customers at /app/customers).
Developer key
A ko_ key, sent as Authorization: Bearer ko_<key>. This is what you mint below. It bills your developer wallet.
Customer
A cus_ record you attribute spend to with the FC-Customer header. Created on first use unless you disable lazy-create.
No test mode

There is no test-vs-live key distinction. A ko_ key is a ko_ key and every successful call spends real wallet balance. Top up a small amount and watch the X-Ringside-Wallet-Balance header to stay oriented while you experiment.

Step 1: Create a key and export it

  1. 1
    Mint the key

    Open /app/api-keys, create a key and grant it the api:chat scope (chat completions check for that scope on every request). The full token is shown once, at creation. It is SHA-256 hashed at rest, so we cannot show it again. Copy it now.

  2. 2
    Confirm the format

    A developer key is literally the string ko_ followed by 64 hex characters. If your copy is shorter, you grabbed the displayed prefix (the first 11 chars) rather than the whole token.

  3. 3
    Export it
    export FC_API_KEY="ko_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
Ringside developer-key scopes
ScopeGrants
api:chatChat completions and the other inference endpoints. Required for this quickstart.
api:readRead customers, logs, usage and other GET routes.
api:writeCreate and mutate customers, vector stores and the like.
api:webhooksManage webhook endpoints.
api:client_tokensMint browser-safe Client tokens via POST /v1/customers/{id}/client_tokens.
Keep keys server-side

A ko_ developer key spends your wallet and carries whatever scopes you granted it. Never ship one to a browser or mobile app. For client-side use, mint a short-lived Client token instead (see authentication).

Step 2: Your first call with curl

The only Ringside-specific thing here is the model ref. A bare name like gpt-4o is rejected with 400 invalid_model_ref. Every model needs a prefix; the simplest is fc:<provider>/<model> for a pinned model. We use fc:openai/gpt-4o-mini.

curl https://api.fightclub.pro/v1/chat/completions \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "fc:openai/gpt-4o-mini",
    "messages": [
      { "role": "user", "content": "In one sentence, what is a tarpit?" }
    ]
  }'

The body is the standard OpenAI envelope. The id carries a chatcmpl- prefix and the usage object reports tokens exactly as OpenAI does.

{
  "id": "chatcmpl-9f3a1c7d8e2b4a6c1f0d5e2a",
  "object": "chat.completion",
  "created": 1750800000,
  "model": "fc:openai/gpt-4o-mini",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "A tarpit is a service that deliberately answers slowly to waste an attacker's time and tie up their connections."
      },
      "finish_reason": "stop",
      "logprobs": null
    }
  ],
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 24,
    "total_tokens": 42
  }
}

Most of the Ringside metadata rides in the response headers rather than the body. Run the same call with curl -i (or -D -) and you get the metering and routing headers. The two you will watch most are the wallet headers.

Response headers worth reading on a chat call
HeaderMeaning
X-Request-IdThe req_ id for this call. It is a header, never a body field. Quote it in support tickets and use it to find the call in the logs.
X-Ringside-Wallet-BalanceWallet balance after this call, in dollars. Present when the call attributed to a customer wallet; otherwise read your developer-wallet balance in the dashboard.
X-Ringside-Wallet-DeductedThe exact charge for this call, in dollars.
X-Ringside-Model-ResolvedThe concrete fc: model a match: / slot: / dyn: ref resolved to. Identity for a pinned fc: ref.
X-RateLimit-RemainingRequests left in the current window. There is no X-RateLimit-Limit or X-RateLimit-Reset.

Step 3: The same call through the OpenAI SDK

Because the surface is OpenAI-compatible, the only edits to existing SDK code are the base URL, the key and the model prefix. No Ringside SDK required for plain inference. Set base_url (Python) or baseURL (Node) to https://api.fightclub.pro/v1 and pass your ko_ key as the API key; the SDK puts it in the Authorization: Bearer header for you.

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["FC_API_KEY"],
    base_url="https://api.fightclub.pro/v1",
)

resp = client.chat.completions.create(
    model="fc:openai/gpt-4o-mini",
    messages=[{"role": "user", "content": "In one sentence, what is a tarpit?"}],
)
print(resp.choices[0].message.content)
Reading Ringside headers from the SDK

The OpenAI SDKs expose raw responses so you can still read the X-Ringside-* headers. In Python use client.chat.completions.with_raw_response.create(...) and read .headers; in Node use client.chat.completions.create(...).asResponse(). For typed helpers around the Ringside-only primitives (the match: builder, customers CRUD, webhook signature verification) install the official SDK: pip install ringside or npm install @ringside/sdk.

Step 4: Attribute the spend to a customer

Add one header and the call is billed against a customer rather than sitting as undifferentiated developer spend. FC-Customer accepts a cus_ id, an ext:<external_id> reference or a plain external id string. If the customer does not exist yet, Ringside lazy-creates it on the spot, so you can start tagging by your own user ids before you have created a single customer record.

curl https://api.fightclub.pro/v1/chat/completions \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "FC-Customer: ext:acme-corp" \
  -d '{
    "model": "fc:openai/gpt-4o-mini",
    "messages": [
      { "role": "user", "content": "In one sentence, what is a tarpit?" }
    ]
  }'

On the first call with FC-Customer: ext:acme-corp, Ringside creates a customer (say cus_42) with that external id and attributes the spend to it. The customer now appears under /app/customers and the response carries X-Ringside-Wallet-Balance / X-Ringside-Wallet-Deducted for that customer wallet. Subsequent calls with the same value resolve to the same cus_42 with no extra round trip.

Customer attribution headers
HeaderValueEffect
FC-Customercus_42, ext:acme-corp or acme-corpAttributes the call. Lazy-creates the customer if unknown.
FC-Customer-StricttrueDisables lazy-create. An unknown id then returns 404 not_found_error instead of creating a record.
X-FC-Billable-Amountinteger micro-dollarsOverride the metered charge (resale / markup). Margin is billable - raw cost.
Budget and wallet failures

A customer can carry a monthly_budget_usd; once spent, calls return 402 customer_budget_exceeded. If the customer holds a prepaid wallet and it is empty, you get 402 customer_wallet_empty. With no customer attribution, an empty developer wallet returns 402 wallet_empty. Branch on the code field, never the human-readable message.

Step 5: Find the call in the dashboard

  1. 01Grab the X-Request-Id value from the response headers (for example req_8c2f...).
  2. 02Open /app/logs.
  3. 03Find the row for that request id. It shows the resolved model, the customer it was attributed to, the token usage and the exact cost (the same figure as X-Ringside-Wallet-Deducted).
  4. 04Click through to the full request / response detail to confirm the attribution landed on cus_42.

If a call is missing from the logs, it almost always failed before metering. Re-read the response: an auth failure (Step 6) or a 400 validation error never reaches the billing path and so never produces a log row. A successful 200 always does.

Failure modes you will hit first

Every error shares one shape. type, code and message are always present; param appears on field-validation errors and retry_after_seconds on rate-limit errors. Write your handling against code.

{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_model_ref",
    "message": "Model ref must start with 'fc:', 'slot:', or 'match:'. Got: gpt-4o",
    "param": "model"
  }
}
Common quickstart errors
StatuscodeCause
401missing_tokenNo Authorization header.
401invalid_auth_schemeThe scheme is not Bearer or Client . Message: "Authorization header must start with 'Bearer ' or 'Client '".
401invalid_token_formatBearer token does not start with ko_.
401invalid_tokenKey is unknown, revoked or expired.
403insufficient_scopeThe key lacks api:chat. Re-mint with that scope.
400invalid_model_refModel is a bare name. Add an fc: / match: / slot: / dyn: prefix.
400missing_messagesmessages is absent or empty.
402wallet_emptyDeveloper wallet has no balance. Top up.
404not_found_errorCross-tenant id, or an unknown FC-Customer under FC-Customer-Strict: true. We return 404 (not 403) so ids are not enumerable.
429rate_limit_errorA rate-limit layer tripped. Read Retry-After (integer seconds) and retry_after_seconds in the body.
Retrying safely

To make a retry idempotent, send an idempotency-key header (lowercase, that exact spelling). The replay window is 24 hours, scoped to (developer, customer, key). One caveat: you cannot combine an idempotency-key with a model array ("model": [...]) cost cascade; that returns 400 fallback_with_idempotency_unsupported.

Where to go next