Developer Docs

The public data API.

Your tournament data, wherever you need it.

FixturePro serves the public schedule, standings, matches, clubs and teams as JSON. Use it to power a scoreboard widget, a club website or a mobile app. Every endpoint is read-only, needs no API key, and is gated on the domain allowlist the organiser configures in Settings.

Getting started

Allowlist your domain first

  1. 1Find the tournament id. It is the uuid in the public page URL, e.g. /t/{tournamentId}/schedule — also shown in the dashboard under Settings → Public sharing & data API.
  2. 2Allowlist the domain that will call the API. In the same settings section, add the exact host (e.g. scoreboard.example) under Allowed domains. Subdomains are not included — allow each host exactly as it calls. While the list is empty the API is blocked for everyone.
  3. 3Call the endpoints from that domain. The call must carry an Origin or Referer header on the allowlist — browsers send Origin automatically on cross-site fetches. Server-to-server calls without either header are rejected, so this is a browser-widget API, not a token-based API.
Response format

Every response uses one envelope

Success responses carry Cache-Control: public, max-age=60 — safe to cache for a minute. Error responses are never cached.

Success
{
  "success": true,
  "data": {
    "id": "club-uuid",
    "name": "Arsenal FC",
    "logoUrl": null,
    "teams": [{ "id": "team-uuid", "name": "Arsenal A" }]
  },
  "error": null,
  "meta": { "generatedAt": "2026-09-21T02:30:00.000Z" }
}
Error
{
  "success": false,
  "data": null,
  "error": {
    "code": "origin_not_allowed",
    "message": "This domain is not on the tournament's allowlist."
  },
  "meta": { "generatedAt": "2026-09-21T02:30:00.000Z" }
}
  • CORS: allowed requests echo your origin in Access-Control-Allow-Origin, and an OPTIONS preflight answers 204 with the same headers. Disallowed origins get 403 on both.
  • Errors: 404 tournament_not_found for an unknown or unpublished tournament; 403 origin_not_allowed when the calling domain is not allowlisted; 404 club_not_found / 404 team_not_found for ids from another tournament.
Reference

Endpoints

All paths below are relative to /api/public/tournaments/{tournamentId}. All are GET-only.

GET/scheduleFixtures grouped by day. Filters: league, division, round, date.
GET/standingsLeague tables per division. Filters: league, division, round.
GET/matches/{matchId}One match with its events (and player names only if the organiser allows).
GET/clubsParticipating clubs with their teams. Filter: letter.
GET/clubs/{clubId}One club with its teams.
GET/teams/{teamId}Team detail: club, division/league and squad (privacy-toggled).
Filtering

Narrow the data with query parameters

Every filter is optional and tolerant — an invalid or unknown value is ignored and you get the full unfiltered data, never an error. Filters combine freely, e.g. division together with date on the schedule.

  • date — /schedule only. A calendar date yyyy-mm-dd returns just that UTC match day; date=all returns every match day. Impossible dates are ignored. Note the difference from the schedule page, which opens on the current match day or the latest past one: the JSON endpoint applies no default — an absent date returns all days.
  • league, division, round — /schedule and /standings. league and round take uuid ids; division takes the division NAME exactly as the public pages show it (URL-encoded, e.g. division=Division%201 — one name spans same-named divisions across leagues; a legacy division uuid from older share links still resolves). While any of the three is active, knockout (bracket) matches are excluded because they carry no division.
  • letter — /clubs only. A single character a–z, case-insensitive; anything else returns the whole list.
  • Finding ids. The unfiltered responses are self-describing: /schedule matches carry divisionId and leagueId — the ids behind each fixture's divisionName — plus phaseId (the round id); /standings groups carry leagueId and divisionId; and /clubs carries every team id.
Schedule — one division, one match day
curl -s \
  -H "Origin: https://scoreboard.example" \
  "https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/schedule?division=Division%201&date=2026-09-26"
Schedule — every match day
curl -s \
  -H "Origin: https://scoreboard.example" \
  "https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/schedule?date=all"
Examples

Copy-paste requests

Schedule
curl -s \
  -H "Origin: https://scoreboard.example" \
  "https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/schedule"
Clubs, filtered by letter
curl -s \
  -H "Origin: https://scoreboard.example" \
  "https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/clubs?letter=a"
Team detail
curl -s \
  -H "Origin: https://scoreboard.example" \
  "https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/teams/{teamId}"
TypeScript / JavaScript
// On a page served from an allowlisted domain — the browser adds Origin
// automatically; you never set it by hand:
const res = await fetch(
  'https://your-fixturepro-domain/api/public/tournaments/{tournamentId}/clubs?letter=a',
)
const { success, data, error } = await res.json()
if (!success) throw new Error(error.code)
const names = data.map((club) => club.name)
Data & privacy

What is public, and what is not

  • Squads: the players array in /teams/{teamId} is empty unless the organiser enabled “Show player names in public match events”. Photo urls additionally require “Also show player photos on the public team page” — they are short-lived signed urls that expire after one hour.
  • Match events: player names in /matches/{matchId} appear only while the names toggle is on; otherwise events carry type, minute and notes only.
  • Foreign ids: a club or team id from a different tournament resolves to 404 — responses never leak another tournament's data.

Rate limiting is not yet implemented (tracked in the product backlog). Please be considerate: the max-age=60 cache header exists so widgets can poll every minute instead of every load. Start from /clubs to discover team ids, then fetch /teams/{teamId} per team. Questions? The organiser of a tournament can share their public page URLs — start at /solutions.