Populate
How the populate query parameter embeds relations, the exact allow-list per endpoint, the defaults each endpoint applies, and the rules for nested paths.
Relations are opt-in. By default each endpoint returns its own columns plus a
small set of relations; anything else you ask for with populate, a
comma-separated list of allow-listed paths. A relation key is absent from the
JSON entirely unless it was populated — so 'divisions' in tournament is a
reliable test for "did I ask for this?", and an unknown path is a 400 rather
than a silently ignored parameter.
Syntax
GET /v1/external/tournaments/{idx}?populate=club,divisions,divisions.rounds- Comma-separated, no spaces.
- Order does not matter.
- Repeating a value is harmless.
- A value outside the endpoint's allow-list is a
400— see Errors.
populate replaces the default, it does not extend it
The moment you send a populate parameter, the endpoint's default list is
gone. GET /v1/external/tournaments/{idx} returns club and divisions when
you send nothing; ?populate=divisions returns divisions and no club. If
you want both, say both.
Nested paths need their parents
Paths are dotted: draws.rounds.games.score.sets. You do not have to list the
ancestors — the API expands them for you, so
populate=draws.rounds.games.score.sets implies draws, draws.rounds,
draws.rounds.games and draws.rounds.games.score. Listing them explicitly
changes nothing.
The reverse is not true: populating a parent does not bring its children.
populate=games gives you games whose homeTeam and score keys are missing;
you need games.homeTeam and games.score for those.
Per-endpoint allow-lists
GET /v1/external/tournaments
List rows stay deliberately shallow — this is the one endpoint that paginates, so a fat row multiplies by 100.
| Allowed | Default |
|---|---|
club | (none) |
GET /v1/external/tournaments/{idx}
| Allowed | Default |
|---|---|
club, club.courts, divisions, divisions.rounds, sponsors, circuit, circuitCategory, rankedTiebreakRules | club,divisions |
GET /v1/external/tournaments/{idx}/divisions
| Allowed | Default |
|---|---|
rounds, teams, teams.playerProfiles, teams.leagueTeam, circuitCategory | rounds |
GET /v1/external/tournaments/{idx}/teams
| Allowed | Default |
|---|---|
playerProfiles, captainProfile, leagueTeam, division | playerProfiles,division |
GET /v1/external/rounds/{idx}
The richest endpoint, and the one whose default is worth knowing by heart.
Allowed:
division
pools
pools.teams
pools.standings
pools.standings.team
pools.standings.teamStats
games
games.homeTeam
games.awayTeam
games.winner
games.court
games.score
games.score.sets
draws
draws.rounds
draws.rounds.games
draws.rounds.games.homeTeam
draws.rounds.games.awayTeam
draws.rounds.games.winner
draws.rounds.games.court
draws.rounds.games.score
draws.rounds.games.score.setsDefault when you send no populate:
division
pools
pools.teams
games
games.homeTeam
games.awayTeam
games.winner
draws.rounds
draws.rounds.games
draws.rounds.games.homeTeam
draws.rounds.games.awayTeam
draws.rounds.games.winner
draws.rounds.games.score
draws.rounds.games.score.setsThe default gives bracket scores but not flat-list scores
Note the asymmetry: the default populates draws.rounds.games.score (+
sets) but not games.score. If you are reading results off the flat
games array, ask for games.score,games.score.sets explicitly — and
remember that doing so replaces the whole default list, so re-list everything
else you need.
Populate keys are not always response keys
Three paths embed under a different name than the one you request:
| You populate | It appears as |
|---|---|
playerProfiles (or teams.playerProfiles) | players: [{ idx, firstName, lastName, avatar }] |
captainProfile | captainProfileIdx: string | null |
pools.standings.teamStats | standings[].teamStats |
Worked examples
A public draw sheet for one knockout round — brackets, winners and scores, no group tables:
curl -s -G https://api-production-ea80.up.railway.app/v1/external/rounds/2e7f5b10-8c94-4d63-a1b8-7f0e3d6c2a95 \
-H "Authorization: Bearer $SETTO_API_TOKEN" \
-d populate=draws.rounds.games.homeTeam,draws.rounds.games.awayTeam,draws.rounds.games.winner,draws.rounds.games.score.setsA group-stage standings table:
curl -s -G https://api-production-ea80.up.railway.app/v1/external/rounds/9d3c6a84-7e15-4b02-8f6d-1c4a9e7b53f2 \
-H "Authorization: Bearer $SETTO_API_TOKEN" \
-d populate=pools.standings.team,pools.standings.teamStatsA full category listing with every registered pair and its players:
curl -s -G https://api-production-ea80.up.railway.app/v1/external/tournaments/3c9b7f52-6d41-4a8e-b1f0-2e7d5c9a4b18/divisions \
-H "Authorization: Bearer $SETTO_API_TOKEN" \
-d populate=teams,teams.playerProfilesCost
Each populated path is more joins and more rows. Three habits keep responses fast:
- Ask for the leaves you render, nothing else.
pools.standings.teamStatswithoutpools.standings.teamis a legal, cheaper request if you already know the teams. - Do not populate on the list endpoint unless you need the club. Fetch the ids from the list, then fetch the one tournament you are rendering.
- Cache. Round payloads change only when a score is entered. Combine a short TTL with conditional requests and one round page costs you a handful of requests an hour instead of one per visitor.