Rollare

Public API

Read tournament and league data over a plain REST API, generate brackets statelessly, and manage your API keys.

The Rollare Public API is a versioned REST interface at https://rollare.com/v1 that exposes the same tournament and league data you see on public pages — brackets, matches, standings, schedules — as JSON. It exists so you can build on top of Rollare without scraping: embed live standings in a club website, drive a scoreboard overlay from match results, power a Discord bot that announces results, or feed a data pipeline. The API is read-only today: it never exposes entrant personal data, and nothing you call can change a tournament.

This page is the complete guide to the API: how authentication works, what the rate limits are for each tier, how errors and pagination behave, and a reference for every endpoint. If you prefer a machine-readable specification, the full OpenAPI 3.1 document lives at /api/openapi.json, and an interactive reference (powered by Scalar) is available at /api-docs. Everything documented here is generated from the same contract that the server validates against, so the specification cannot drift from the implementation.

Quickstart

The fastest way to try the API is with curl and no key at all — public read endpoints work anonymously with a small quota:

curl https://rollare.com/v1/tournaments/summer-cup-2026

Replace summer-cup-2026 with any tournament slug. You get back the tournament's metadata, participant list, divisions, and registration status. To list every match with scores:

curl https://rollare.com/v1/tournaments/summer-cup-2026/matches

And to generate a bracket without touching any stored data — the same engines behind the free bracket generator tools:

curl -X POST https://rollare.com/v1/bracket/generate \
  -H "Content-Type: application/json" \
  -d '{"format": "single_elimination", "teams": [{"name": "A"}, {"name": "B"}, {"name": "C"}, {"name": "D"}]}'

Every response includes an X-Request-Id header (quote it when contacting support), rate-limit headers, and, for reads, a short Cache-Control lifetime so your own CDN can share the cache.

Authentication

Authentication is optional. Anonymous callers get a low quota, which is enough to evaluate the API or serve a small embed. For regular use, create an API key in your Rollare dashboard under Studio → Settings → API Keys. Keys look like rl_live_xxxxxxxxxxxx… and are shown exactly once at creation — store them in a secret manager, not in source control.

Send the key in the Authorization header:

curl -H "Authorization: Bearer rl_live_YOUR_KEY" https://rollare.com/v1/leagues/winter-series/standings

There is one fallback: ?api_key=YOUR_KEY as a query parameter, for environments where you cannot set headers (some embed widgets). Prefer the header whenever you can — query strings tend to leak into server logs and browser history.

Keys can be revoked at any time from the same dashboard page, and you can hold up to 5 active keys, which makes per-integration keys practical: one key for the club website, one for the Discord bot, one for testing. Revoking a key takes effect within five minutes at most; if a key leaks, revoke it and create a fresh one.

Your tier determines your quota. Keys are created on the Free tier, and two things can raise it: an active Community or Pro subscription upgrades your keys to Standard automatically, and the Rollare team can grant a custom Partner tier for high-volume integrations — contact us with a short description of what you are building.

Rate limits

Rate limits are enforced per key (or per IP for anonymous calls) over two sliding-fixed windows: a per-minute window that absorbs bursts and a daily window that caps abuse. Every response carries three headers:

HeaderMeaning
X-RateLimit-LimitQuota of the tightest active window for your tier
X-RateLimit-RemainingRequests left in that window (omitted on cache hits — hits are free)
X-RateLimit-ResetUnix timestamp when the window resets

The current quotas:

TierReadsBracket generate
Anonymous (no key)60/min · 1,000/day20/day
Free key120/min · 10,000/day200/day
Standard (Community/Pro)300/min · 100,000/day2,000/day
PartnerCustomCustom

When you exceed a limit the API returns 429 with a Retry-After header telling you exactly how many seconds to wait. Treat 429 as a signal to back off, not to retry harder: clients that hammer a exhausted limit only deepen the deficit. The single most effective optimization is also the simplest — cache GET responses on your side for the lifetime of the Cache-Control header (30–60 seconds). Embed widgets and dashboards that poll once a minute effectively never approach a limit.

Errors

Errors use one consistent envelope, so a single handler covers every failure:

{
  "error": {
    "code": "not_found",
    "message": "Resource not found. Only published tournaments and leagues are visible through the public API.",
    "request_id": "0b8f6c30-…"
  }
}
StatuscodeWhen
400invalid_requestMalformed query, body, or cursor
401unauthorizedKey missing/invalid when the endpoint requires one, or a revoked key was presented
404not_foundUnknown slug, or the tournament/league is a draft or deleted
429rate_limitedQuota exceeded — honor Retry-After
500internal_errorOur fault; retry once, then back off

Draft and deleted tournaments deliberately return 404 (not 403) — the public API sees exactly what the public web sees, no more. Note that archived tournaments remain readable: archiving locks editing, it does not hide history, matching the behavior of public tournament pages.

Pagination

List endpoints (currently matches) use cursor pagination with a stable sort. Pass ?limit= (1–250, default 100) and read nextCursor from the response; when it is null there are no more pages. Pass it back as ?cursor= to fetch the next page. Cursors are opaque strings — do not parse or construct them, just echo them back. Data is sorted by (round, matchNumber), so paging through a tournament yields a deterministic order even while results are being recorded.

Endpoints

GET /v1/tournaments/{slug}

Tournament metadata: name, format, status, category, venue, start time, organizer display name, the full participant list (id, name, seed, logo), division summaries, and registration status with the approved count. This is the lightest call and the right starting point for an embed.

GET /v1/tournaments/{slug}/matches

Every match with its round, bracket type, status, venue, scheduled time, both team references, final score (including per-set scores where recorded), and the winner. Filter with ?round=, ?status=completed, or ?division= (division id). Pagination as described above.

GET /v1/tournaments/{slug}/standings

Derived standings — the shape depends on the format, and the response's kind field tells you which you got:

  • points — a flat table (rank, played, wins, draws, losses, points) for round robin and Swiss formats. Swiss rows add Buchholz (opponentPoints) and bye counts. Round-robin rows add goals for/against/difference.
  • groups — the same table, one array per group, for group+knockout formats; qualifiers lists who advanced.
  • placements — final rankings for elimination formats: champion (1), runner-up (2), and the third-place playoff result (3/4) for single elimination when one was played.

Standings are computed server-side from the recorded results by the same engines the app uses, so what your integration shows always matches the public page.

GET /v1/tournaments/{slug}/schedule

Only matches that have been scheduled, sorted by start time, with venue and referee display names. Referees appear as display names only — never account identities.

GET /v1/leagues/{slug} and GET /v1/leagues/{slug}/standings

Series/league metadata with the ordered list of member tournaments (station numbers, formats, statuses, which events count toward the standings), and the cross-tournament standings computed under the league's points rules. Leagues with no published events return 404.

POST /v1/bracket/generate

Stateless bracket generation. Pass a format (all six are supported), 2–128 teams, and optional settings (doubleRoundRobin, swissRounds, groupCount); you get the full structure back as rounds of matches with stable matchKeys — the same key space the app itself uses, so generated structures and real tournaments speak the same language. Nothing is stored. Note that Swiss pairings for rounds beyond recorded results are provisional by nature (pairings depend on prior results); the response marks those rounds in provisionalRounds.

Privacy and terms

Three boundaries hold across the entire API. First, only public data: drafts and deleted events are invisible, exactly as on the web. Second, no personal data of entrants: registration identities, emails, and custom form answers never appear in any response; organizers and referees appear as display names only. Third, attribution: if you build a public integration, an honest "data from Rollare" credit (with a link) is required for partner-tier keys and appreciated everywhere. Reselling bulk raw data is not permitted — the API is for building experiences, not for replicating the catalog. The Terms of Service cover the fine print.

FAQ

Do I need a key to embed standings in my website? Not necessarily — anonymous quota is sized for small embeds. If your page gets real traffic, create a free key: same price, ten times the quota.

Can the API create or edit tournaments? No. Write access (including webhook delivery of results) is on the roadmap but not available; the current API is strictly read-only plus stateless generation.

Is there an official SDK? The OpenAPI document is complete and current, so any OpenAPI generator (openapi-typescript, openapi-generator) produces a typed client for your language. A first-party TypeScript client may follow when there is demand.

How fresh is the data? GET responses are edge-cached for 30–60 seconds. For live-score use cases, poll at most once a minute; faster polling buys you nothing (you will read the same cached bytes) and only spends quota.