Skip to main content
Documentation

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 curl calls, and required for key management.

The public competitions endpoints are the exception: they need no credential at all. Don't send a key to them; a key baked into a shared mod or script is readable by everyone who has it.

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

A working personal API key needs two entitlements on your account: api.personal.read, which lets you mint a key, and api.personal.rate_limit above 0, which is your hourly request allowance. With api.personal.read alone you can mint a key, but every call with it returns 403 "No rate-limit allowance". Both are off by default while the Personal API is in early access. Contact us to enable them 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:

FieldRequiredNotes
nameyesA label so you can recognize the key later.
terms_versionyesThe API terms version you're accepting — see /api-terms.
terms_acceptedyesMust be true; records your acceptance of the terms.
rate_limit_rphnoPer-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, and the public competitions endpoints allow 60 requests/minute per IP. Exceeding either returns 429 with an X-RateLimit-Reset epoch second — honor it.

Where each credential works

SurfaceAccepts
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
Public competitions (/api/v1/public/competitions*)Nothing — no credential needed

Read the acceptable use terms before you build — publicly posting the aggregate exports requires attribution.