Authentication
How to authenticate requests to the Tabletop League API.
Every authenticated endpoint accepts one of two credentials:
- A personal API key —
Authorization: Bearer ttl_user_…— for scripts and integrations. - A logged-in browser session (Supabase JWT cookie) — handy for one-off
curlcalls, and required for key management.
Keys carry no scopes. A key acts as you, and only ever returns your own data or public aggregate data. There is no read/write/admin split.
Early access — key minting is gated
Minting a personal API key requires the api.personal.read
entitlement, which is off by default while the Personal API is in early
access. Contact us to enable it on your account. You don't need a key to
download the aggregate statistics exports — a signed-in session works. See
Public statistics exports.
Minting a key
There is no settings screen for keys — mint and manage them through the key-management API, authenticated with your browser session cookie. (Bearer keys can't manage keys, so a leaked key can't mint more.)
curl -X POST https://tabletopleague.com/api/v1/me/keys \
--cookie cookies.txt \
-H "Content-Type: application/json" \
-d '{"name":"my-integration","terms_version":"2026-01-01","terms_accepted":true}'
The plaintext key is returned once — store it immediately, it can't be retrieved again:
{
"success": true,
"data": {
"id": "…",
"plaintext": "ttl_user_…",
"key_prefix": "ttl_user_…",
"rate_limit_rph": 60
},
"_meta": { "request_id": "…", "timestamp": "…", "terms_url": "/api-terms" }
}
Request body:
| Field | Required | Notes |
| ---------------- | -------- | ---------------------------------------------------------------------- |
| name | yes | A label so you can recognize the key later. |
| terms_version | yes | The API terms version you're accepting — see /api-terms. |
| terms_accepted | yes | Must be true; records your acceptance of the terms. |
| rate_limit_rph | no | Per-hour request cap for this key (default 60). |
Every key starts with the ttl_user_ prefix.
Using a key
Send it as a Bearer token. This is the only supported way to pass a key — never put it in a query string:
curl -H "Authorization: Bearer ttl_user_xxxxx" \
https://tabletopleague.com/api/v1/me/stats
Listing and revoking keys
Both require your session cookie (not a Bearer key):
# List active keys — prefixes and metadata only, never the secret
curl --cookie cookies.txt https://tabletopleague.com/api/v1/me/keys
# Revoke a key by its id (from the list above)
curl -X DELETE --cookie cookies.txt \
https://tabletopleague.com/api/v1/me/keys/<key-id>
Rate limits
The Personal API applies a per-hour cap (default 60 requests/hour) and
returns X-RateLimit-* headers. The public statistics exports add a separate
30 requests/minute per IP limit. Exceeding either returns 429 with an
X-RateLimit-Reset epoch second — honor it.
Where each credential works
| Surface | Accepts |
| ---------------------------------------------------------------------------------- | ---------------------------- |
| Personal API (/api/v1/me/*) | Bearer key or session cookie |
| Public statistics exports (/api/v1/public/hero-stats*) | Bearer key or session cookie |
| Key management (/api/v1/me/keys) | Session cookie only |
Read the acceptable use terms before you build — publicly posting the aggregate exports requires attribution.