POST
/v1/customers/{id}/client_tokensClient 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
| Name | Type | Default | Description |
|---|---|---|---|
| scope | string[] | ["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_seconds | integer | 900 | Token lifetime in seconds. Min 60, max 3600 (1 h). |
| rpm_limit | integer | 60 | Per-token requests-per-minute cap. Min 1, max 600, and silently capped to the customer's own rpm if that is lower. |
| origin_allowlist | string[] | optional | Allowed browser Origins (each <=512 chars). A request whose Origin is not in the list is rejected. Omit to allow any origin. |
| bind_ip | boolean | false | Pin the token to the IP that minted it (the client IP must be resolvable, else 400 bind_ip_missing_client_ip). |
* required.
Headers
| Header | Dir | Description |
|---|---|---|
| Authorization | req → | Bearer <api_key>. The key must hold the api:client_tokens scope; a client token cannot mint another. |
Response fields
| Name | Type | Description |
|---|---|---|
| token | string | The signed client token (a JWT). Send it from the browser as Authorization: Client <token> (note: the Client scheme, not Bearer). |
| jti | string | Token id (use it to find this token in the list endpoint). |
| expires_at | string | ISO 8601 expiry. |
| scope | string[] | The granted scope. |
| rpm_limit | integer | The effective rpm cap (after customer-cap clamping). |
| customer_id | string | The customer this token spends for. |
Errors
- 401
missing_tokenNo Authorization header was sent. - 401
invalid_auth_schemeThe scheme was neither Bearer nor Client. - 401
invalid_token_formatA Bearer token not prefixed ko_. - 401
invalid_tokenThe API key is unknown, revoked or expired. - 403
insufficient_scopeThe key is valid but lacks the required scope. - 403
insufficient_scopeKey lacks api:client_tokens. - 400
invalid_scopescope contains an unknown value. - 400
empty_scopescope was an empty array. - 400
invalid_ttlttl_seconds outside 60..3600. - 400
invalid_rpm_limitrpm_limit outside 1..600. - 400
rpm_exceeds_customer_capRequested rpm_limit is above the customer's own cap. - 400
invalid_origin_allowlistorigin_allowlist is not an array of strings <=512 chars. - 400
bind_ip_missing_client_ipbind_ip set but no client IP could be resolved. - 429
mint_rate_limitMore than 100 mints/min for this customer; back off. - 404
not_foundNo such customer (also cross-tenant).
See the full error reference.
Related routes
| GET | /v1/customers/{id}/client_tokens | List 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_all | Revoke 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.