Prefer Swagger UI? Click hereThe same API in the classic OpenAPI explorer.
POST/v1/customers/{id}/client_tokens

Client Tokens

Mint a short-lived, browser-safe token scoped to one Customer so a frontend can call Ringside directly without ever shipping your secret API key. The token defaults to 15-minute life and chat-only scope, carries its own rpm limit, and can be pinned to an origin allow-list and the caller IP. Minted under the customer: /v1/customers/{id}/client_tokens. Requires a key with the api:client_tokens scope.

Request
HTTP
POST
URL
/v1/customers/{id}/client_tokens
Auth
api_key
Try it
# server-side: mint a 10-minute, origin-locked token for the browser
curl https://api.fightclub.pro/v1/customers/cus_42/client_tokens \
  -H "Authorization: Bearer $FC_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "ttl_seconds": 600,
    "scope": ["chat"],
    "rpm_limit": 20,
    "origin_allowlist": ["https://app.example.com"]
  }'
# => { "token":"...", "jti":"ctok_...", "expires_at":"...", "customer_id":"cus_42" }

# browser: call chat with the minted token (no api key in the page)
curl https://api.fightclub.pro/v1/chat/completions \
  -H "Authorization: Client <token>" -H "Origin: https://app.example.com" \
  -d '{"model":"fc:openai/gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'

Example response

{
  "token": "eyJhbGciOiJFZERTQSJ9...",
  "jti": "ctok_5Kp2",
  "customer_id": "cus_42",
  "scope": [ "chat" ],
  "rpm_limit": 20,
  "expires_at": "2026-06-24T12:10:00.000Z"
}

A representative 200 body. Ids and timestamps are illustrative.

Body parameters

NameTypeDefaultDescription
scopestring[]["chat"]What the token may call. Currently chat is the meaningful scope; the token is rejected (403 endpoint_not_allowed_for_client_token) at any endpoint outside its scope.
ttl_secondsinteger900Token lifetime in seconds. Min 60, max 3600 (1 h).
rpm_limitinteger60Per-token requests-per-minute cap. Min 1, max 600, and silently capped to the customer's own rpm if that is lower.
origin_allowliststring[]optionalAllowed browser Origins (each <=512 chars). A request whose Origin is not in the list is rejected. Omit to allow any origin.
bind_ipbooleanfalsePin the token to the IP that minted it (the client IP must be resolvable, else 400 bind_ip_missing_client_ip).

* required.

Headers

HeaderDirDescription
Authorizationreq →Bearer <api_key>. The key must hold the api:client_tokens scope; a client token cannot mint another.

Response fields

NameTypeDescription
tokenstringThe signed client token (a JWT). Send it from the browser as Authorization: Client <token> (note: the Client scheme, not Bearer).
jtistringToken id (use it to find this token in the list endpoint).
expires_atstringISO 8601 expiry.
scopestring[]The granted scope.
rpm_limitintegerThe effective rpm cap (after customer-cap clamping).
customer_idstringThe customer this token spends for.

Errors

  • 401missing_tokenNo Authorization header was sent.
  • 401invalid_auth_schemeThe scheme was neither Bearer nor Client.
  • 401invalid_token_formatA Bearer token not prefixed ko_.
  • 401invalid_tokenThe API key is unknown, revoked or expired.
  • 403insufficient_scopeThe key is valid but lacks the required scope.
  • 403insufficient_scopeKey lacks api:client_tokens.
  • 400invalid_scopescope contains an unknown value.
  • 400empty_scopescope was an empty array.
  • 400invalid_ttlttl_seconds outside 60..3600.
  • 400invalid_rpm_limitrpm_limit outside 1..600.
  • 400rpm_exceeds_customer_capRequested rpm_limit is above the customer's own cap.
  • 400invalid_origin_allowlistorigin_allowlist is not an array of strings <=512 chars.
  • 400bind_ip_missing_client_ipbind_ip set but no client IP could be resolved.
  • 429mint_rate_limitMore than 100 mints/min for this customer; back off.
  • 404not_foundNo such customer (also cross-tenant).

See the full error reference.

Related routes

GET/v1/customers/{id}/client_tokensList the customer's currently-active tokens: data[{ jti, scope, created_at, expires_at, rpm_limit, origin_allowlist, ip_bound }].
DELETE/v1/customers/{id}/client_tokens/{jti}Revoke one token by its jti (204 No Content).
POST/v1/customers/{id}/client_tokens/revoke_allRevoke every active token for the customer; returns { revoked_count }.

Notes

  • ·Mint server-side (your backend has the api key), then hand the returned token to the browser. Never expose the api key to the frontend.
  • ·The browser calls Ringside with Authorization: Client <token> (the Client scheme is what marks the request as a client token rather than a dev key); spend lands on the customer in the path.
  • ·Tokens are short-lived by design, but you can revoke early: DELETE one by jti, or POST revoke_all to kill them all (e.g. on logout or a suspected leak).
  • ·Minting is rate-limited to 100/min per customer.

Examples