Data model
The SETTO domain tree — organization, tournament, division, round, pool, draw and game — plus the id conventions and the enum values you will switch on.
Everything the External API returns hangs off one tree: an organization owns tournaments; a tournament has divisions (categories) and teams; a division has rounds; a round holds pools (groups, with standings) for round-robin play and draws (brackets) for knockout play; both ultimately contain games, and a finished game has a score with sets. Learn this shape once and the six endpoints stop looking like six unrelated things.
The tree
In prose:
- An Organization owns tournaments. Your token is bound to exactly one.
- A Tournament is one competition. It carries the format (
type), the sport, the lifecyclestatus, dates, and apublicUrl. - A Division is what organizers and players call a categoría: a bracket of
skill level and gender inside the tournament (
Cuarta Fuerza Varonil). It owns the rounds and the price. - A Round is one phase — group play or a knockout stage.
- A Pool is a group inside a round-robin round; it holds the teams in that group and their standings.
- A Draw is a bracket. It contains draw rounds (
Quarterfinals,Semifinals, …) inorder, and each draw round contains its games. - A Game is one match. Finished games carry a score, which carries its sets.
- A Team is a registered entry: a pair in doubles, a single player in singles, the whole squad in fútbol, and in team leagues a squad linked to a league team.
Identifiers
Every resource exposes a UUID v4 as idx. That is the id you pass in a path
(/v1/external/tournaments/{idx}) and the id you store on your side. The numeric
primary keys SETTO uses internally are never exposed.
Tournaments carry two extra identifiers:
| Field | Example | Use |
|---|---|---|
slug | torneo-apertura-2026 | Human-readable, unique, stable |
publicUrl | https://www.setto.io/t/torneo-apertura-2026 | Canonical public page, ready to link |
Endpoints take idx, not slug. Keep the idx from the list call; use the
slug for display and the publicUrl for links.
Tournament
| Field | Notes |
|---|---|
type | Competition format — see below |
sport | PADEL, TENNIS, PICKLEBALL or FUTBOL |
status | Lifecycle — see below |
startDate, endDate | ISO 8601 UTC timestamps |
timezone | IANA zone the schedule was authored in (America/Monterrey), nullable |
isPublic | Whether the public page is reachable — false does not hide it from the API |
isFormatComplete | Whether the organizer finished configuring the format |
matchDays | Number of jornadas, for league-shaped tournaments |
standingColumns | Which statistics the standings table shows — fútbol adds the goal columns |
sportSettings | Fútbol only: modality, extra time, forfeit award — see below. null otherwise |
publicUrl | https://www.setto.io/t/{slug} |
organization | { idx, name, slug } — always present |
type
The four formats you will meet most often:
| Value | Shape |
|---|---|
TOURNAMENT | A one-off event: group stage and/or a bracket |
LEAGUE | A season of jornadas between pairs |
TEAM_LEAGUE | A season between squads; a round may have division: null |
INDIVIDUAL_LEAGUE | A season between individual players |
Specialised formats (Americano, express/lucky-loser and pickleball individual
leagues) carry their own type values. Treat type as an open string: switch on
the values you care about and fall through for the rest, rather than asserting
the set is closed.
status
The lifecycle runs in one direction:
DRAFT → UPCOMING → IN_PROGRESS → COMPLETED → ARCHIVEDAll five are returned. DRAFT and ARCHIVED tournaments are visible to your
token even though the public site hides them — filter with the status query
parameter, or drop them client-side, before you render anything public.
sportSettings
A fútbol (soccer) tournament carries a sportSettings block with the rules the
organizer chose. For PADEL, TENNIS and PICKLEBALL the field is null.
| Field | Notes |
|---|---|
modality | F5, F7, F9, F11 or FUTSAL — squad size and pitch |
extraTime | Whether a tied knockout game goes to extra time before a shootout |
matchMinutes | Regulation playing time, in minutes |
h2hRequiresCompleteSubset | Skip the head-to-head mini-league unless every tied pair has met |
forfeitScore | Goals awarded to the non-offending side on a forfeit |
fairPlayWeights | { yellow, secondYellow, directRed, yellowThenRed }, nullable |
Fútbol tournaments are always type: TOURNAMENT, always registered by teams (a
captain enters the squad), and never part of a circuit — circuit and
circuitCategory stay null.
Division (categoría)
| Field | Notes |
|---|---|
name | Cuarta Fuerza Varonil |
number | Display order; lists are sorted by it ascending |
color | Hex colour the organizer picked, used across SETTO's UI |
isDoubles | true for pairs, false for singles and for fútbol squads |
isVisible | false means the organizer hid this category on the public site |
price | Entry price for this category, nullable |
minParticipants, maxParticipants, maxRegistrations | Capacity, all nullable |
tournament | { idx } back-reference |
Hidden is returned, not filtered
A hidden division comes back with isVisible: false rather than being
dropped. The API's job is to tell you the truth; deciding what to show is
yours. The same applies to rounds.
Round
| Field | Notes |
|---|---|
name | Pool Play, Playoffs or Qualifiers |
type | ROUND_ROBIN_ROUND, SINGLE_ELIMINATION_ROUND or QUALIFIERS_ROUND |
number | Order within the division |
isVisible | Same semantics as a division's |
isSetup | Whether the organizer has generated the round's games |
numberOfSets, proSets, playAllSets, pointsPerSet | Scoring rules |
drawSize, numQualifiers, placements | Bracket sizing |
division | { idx, name } — nullable: a TEAM_LEAGUE round can span the whole tournament |
A round-robin round fills pools; a single-elimination round fills draws. Many
rounds fill both games (the flat list) and the nested bracket structure — the
same game can therefore appear twice in one payload, once under games and once
under draws[].rounds[].games[]. De-duplicate on game.idx.
A fútbol round is always round-robin or single elimination, never qualifiers,
and always one set: numberOfSets is 1, proSets and playAllSets are
false, and pointsPerSet is null.
Pool, standing and team stats
A pool is a group: { idx, name, number }, plus teams and standings when
you populate them. A standing carries rank, awardedPoints, its team
reference, and a teamStats object with points, wins, losses, ties,
forfeits, pointsFor / pointsAgainst, setsPointsFor / setsPointsAgainst
and the two computed differences.
In fútbol those same counters are goals: pointsFor is goals for,
pointsAgainst goals against, and pointsDifference the goal difference. The
set-shaped stats — setsPointsFor, setsPointsAgainst and
setsPointsDifference — are never written for fútbol and stay 0.
tournament.standingColumns picks which of those statistics the table shows. A
fútbol tournament adds GOALS_FOR, GOALS_AGAINST and GOAL_DIFFERENCE —
three columns that read the pointsFor / pointsAgainst / pointsDifference
fields above — in place of the SETS_* / POINTS_* columns, which it cannot
use. The match counters are untouched: a new fútbol tournament is seeded with
GAMES_PLAYED, GAMES_WON, GAMES_TIED, GAMES_LOST and those three goal
columns. Racket sports keep their sets and points columns and never get the
goal ones.
About rank
rank is the stored rank as SETTO last computed it. The public site
applies head-to-head and other configured tiebreaks on top when it renders a
standings table, so a table you build from rank alone can differ from the
one on setto.io. If exact parity matters, order by the tournament's own
tiebreak rules (populate=rankedTiebreakRules on the tournament) rather than
by rank.
Fútbol adds one criterion to that order: H2HL, the head-to-head mini-league.
The teams still level are re-ranked on a sub-table built only from the games
they played against each other — points first, then goal difference, then goals
for inside that sub-table. It is skipped while those teams have not all met,
unless the organizer turned sportSettings.h2hRequiresCompleteSubset off.
Draw and draw round
A draw is one bracket: { idx, placement }. placement distinguishes the
main draw from consolation/third-place brackets. Its draw rounds carry a
label (Quarterfinals, Semifinals, Final) and an order; sort by order
to lay the bracket out left to right.
Game
| Field | Notes |
|---|---|
number, displayNumber | Match numbering; displayNumber is what organizers show |
status | UPCOMING, FINISHED, CANCELLED, FORFEIT or BYE |
time | Scheduled kickoff, ISO 8601 UTC, nullable |
courtNumber, court | Court number and, when populated, the court object |
matchDay | Jornada number, for leagues |
homeTeam, awayTeam, winner | { idx, name } references, each nullable |
homeTeamDetails, awayTeamDetails | The slot's label while its team is unknown — see below |
score | { home, away, sets[] } when populated — see below |
isRetirement | Whether the match ended in a retirement |
Slot labels
In a bracket, a game often exists before its participants are known. Then
homeTeam is null and homeTeamDetails holds the slot's label, which says
where the team will come from:
{
"label": "Winner of Game 3",
"labelEs": "Ganador del juego 3",
"teamNumber": null,
"poolNumber": null,
"poolRank": null,
"sourceGameNumber": 3,
"sourceResult": "WINNER"
}label is the stored key, mostly in English; labelEs is the same label as
the SETTO dashboard words it in Spanish («Ganador del juego 3», «1ro Grupo A»).
For another language, build it from sourceGameNumber / sourceResult (or
poolNumber / poolRank) rather than printing label.
Score and sets
{
"idx": "1b9d7c36-4e58-40af-92c1-6d3f8a05b7e2",
"home": 2,
"away": 1,
"sets": [
{ "idx": "…", "number": 1, "home": 6, "away": 4, "isTieBreak": false },
{ "idx": "…", "number": 2, "home": 3, "away": 6, "isTieBreak": false },
{ "idx": "…", "number": 3, "home": 7, "away": 5, "isTieBreak": false }
]
}score.home / score.away are sets won, not points. Per-set points live in
sets[], ordered by number.
Fútbol scores
A fútbol result reuses that shape with a different reading. score.home and
score.away are regulation goals — the score at full time — and sets
holds exactly one element repeating them, with isTieBreak: false. That
single synthetic set is deliberate: an integration that sums sets[] keeps
working, it just sums goals instead of per-set points.
{
"idx": "7d2c9a41-0b63-4f18-9e57-3a4c8d015f6b",
"home": 2,
"away": 2,
"sets": [
{ "idx": "…", "number": 1, "home": 2, "away": 2, "isTieBreak": false }
],
"extraTime": { "home": 1, "away": 0 },
"penalties": null,
"decisionMethod": "EXTRA_TIME"
}Three fields only fútbol fills; a racket score returns null for all three:
| Field | Notes |
|---|---|
extraTime | { home, away } — goals scored during extra time, or null |
penalties | { home, away } — kicks converted in the shootout, or null |
decisionMethod | REGULATION, EXTRA_TIME, SHOOTOUT or WALKOVER (forfeit) |
Extra time does not accumulate, kicks are not goals
extraTime counts only what was scored in the extra period, so add it to
score.home / score.away for the total: the example above is 2-2 at full
time and a 3-2 win after extra time. A shootout is not scoring at all —
penalties never reaches the standings, so winning 4-3 on kicks leaves both
teams' pointsFor untouched.
A fútbol group game may simply end level: the result stands as a draw and
winner is null. A knockout game cannot — a tie goes to extra time (when the
organizer enabled it) and then to a shootout, and SETTO derives winner from
whichever decider settled it. Read winner and decisionMethod rather than
comparing score.home and score.away, which stay at the full-time goals.
Team
| Field | Notes |
|---|---|
name | Álvarez / Peña for a pair, the player's name in singles, the squad in fútbol |
seed, seedNumber | Seeding; nullable, and sorted nulls-last in list responses |
division | { idx, name }, nullable |
players | [{ idx, firstName, lastName, avatar }] when populated — no contact data, ever |
captainProfileIdx | The captain's player-profile idx, when populated |
leagueTeam | { idx, name, color, logo, position, pointsAdjustment } for team leagues |
isWildCard, qualifierStatus, drawAssignment | Qualifier/wildcard bookkeeping |
picture, club, ranking, description | Optional presentation fields |
In a TEAM_LEAGUE, a team belongs to a league team — the squad
(Alemania, Brasil) that accumulates points across the season.
Dates and time zones
Every timestamp is ISO 8601 in UTC with a Z suffix
(2026-09-18T17:00:00.000Z). The tournament's timezone field tells you which
IANA zone the organizer authored the schedule in; convert into it before showing
a kickoff time, or a Monterrey 19:00 match will read as 01:00 the next day.