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
- 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.
- 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.
- 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.