Tournament lifecycle
Create an organization and a tournament, publish it, close and reopen registration, generate stages, start, complete, cancel and archive.
A tournament moves through a fixed set of states. Each transition is one call, answers with the updated tournament, and publishes an event.
flowchart LR draft -->|publish| published published -->|close registration| registration_closed registration_closed -->|reopen registration| published registration_closed -->|start| in_progress in_progress -->|complete| completed draft -->|cancel| cancelled published -->|cancel| cancelled registration_closed -->|cancel| cancelled in_progress -->|cancel| cancelled draft -->|delete| archived cancelled -->|delete| archived
1. Create an organization
Every tournament belongs to an organization. POST /organizations takes a name (up to 120
characters), a lowercase slug (a-z, 0-9 and single hyphens, up to 80 characters) and an
ownerPrincipalId. With an API key, set ownerPrincipalId to that key's id so the key can manage
the organization's tournaments (see Organization ownership).
curl -X POST "$TOURNAMENT_API_URL/organizations" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"name":"Spring League","slug":"spring-league","ownerPrincipalId":"'"$TANITAR_API_KEY_ID"'"}'| Route | Scope | What it does |
|---|---|---|
GET /organizations | organizations:read | Lists your organizations (cursor, limit). |
GET /organizations/:organizationId | organizations:read | Reads one. |
PATCH /organizations/:organizationId | organizations:mutate | Changes name, slug or ownerPrincipalId. |
POST /organizations/:organizationId/disable | organizations:mutate | Disables it; its tournaments stop being reachable through ownership checks. |
POST /organizations/:organizationId/enable | organizations:mutate | Re-enables it. |
DELETE /organizations/:organizationId | organizations:mutate | Archives it. An archived organization cannot be disabled or archived again. |
2. Create a tournament
POST /tournaments (tournaments:mutate) creates a tournament in draft. The slug is generated
from the name; if it is taken, a short random suffix is added.
| Field | Required | Meaning |
|---|---|---|
organizationId | yes | The owning organization. |
name | yes | Up to 120 characters. |
configurationMode | yes | express, standard or advanced. |
visibility | yes | public or private. Only a public tournament answers GET /tournaments/:slug/public. |
participantMode | yes | solo or team. |
identityModes | yes | Non-empty set of saas and opaque. |
capacity | yes | minEntrants (at least 2), and optionally maxParticipants, maxTeams, teamSize, maxSubstitutes. Stage generation needs a finite capacity. |
registration | yes | opensAt, closesAt (ISO 8601). |
schedule | yes | startsAt, optional endsAt. |
lifecyclePolicy | yes | autoStart, autoComplete, checkInReminderOffsetsMinutes (up to 10 distinct values, 1–10080) and matchForfeitTimeoutMinutes (1–10080 or null). |
checkIn | no | enabled, opensAt, closesAt. |
waitlist | no | enabled, optional maxSize. |
resultPolicy | no | evidenceRequired, drawsAllowed, rankBy and tiebreakers. |
eligibility, customRules | no | Free-form objects. |
rankBy is one of match-wins, game-wins, points, total-score, point-difference.
tiebreakers is an ordered list drawn from head-to-head, head-to-head-points,
head-to-head-point-difference, games-won, point-difference, match-wins, points,
total-score.
curl -X POST "$TOURNAMENT_API_URL/tournaments" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"organizationId": "org_0190f1c2-7a3b-7c4d-8e5f-6a7b8c9d0e1f",
"name": "Spring Cup",
"configurationMode": "standard",
"visibility": "public",
"participantMode": "solo",
"identityModes": ["saas"],
"capacity": { "minEntrants": 4, "maxParticipants": 32 },
"registration": { "opensAt": "2027-03-01T00:00:00Z", "closesAt": "2027-03-10T18:00:00Z" },
"checkIn": { "enabled": true, "opensAt": "2027-03-10T18:00:00Z", "closesAt": "2027-03-10T19:00:00Z" },
"waitlist": { "enabled": true, "maxSize": 8 },
"schedule": { "startsAt": "2027-03-10T19:30:00Z", "endsAt": "2027-03-10T23:00:00Z" },
"lifecyclePolicy": {
"autoStart": false,
"autoComplete": false,
"checkInReminderOffsetsMinutes": [30],
"matchForfeitTimeoutMinutes": 15
}
}'Reading and editing
| Route | Scope | What it does |
|---|---|---|
GET /tournaments | tournaments:read | Lists the tournaments of the organizations you may act for, archived ones excluded (cursor, limit). |
GET /tournaments/:slug | tournaments:read | Reads one. |
GET /tournaments/:slug/public | tournaments:read | The public projection of a public, non-archived tournament: what you show players. |
PATCH /tournaments/:slug | tournaments:mutate | Updates fields. In draft every field above but organizationId, participantMode and identityModes can change, plus slug; after publishing only name, visibility and customRules can. A cancelled or archived tournament cannot be updated. |
3. Publish
POST /tournaments/:slug/publish moves a draft to published and opens enrollment inside the
registration window. Publishing also schedules the automatic work the tournament asked for:
registration closes at registration.closesAt, check-in closes at checkIn.closesAt with the
configured reminders before it, the tournament starts at schedule.startsAt when autoStart is
set, and completes at schedule.endsAt when autoComplete is set. An incomplete configuration is
refused with 422 TOURNAMENT_NOT_PUBLISHABLE.
4. Close and reopen registration
Registration closes on its own at registration.closesAt. To close it earlier, call
POST /tournaments/:slug/registration/close on a published tournament.
POST /tournaments/:slug/registration/reopen returns a registration_closed tournament to
published. It is refused with 422 TOURNAMENT_NOT_READY once check-in close has been processed
or once the configured registration.closesAt has passed.
When check-in is enabled, close it before starting. See Enrollment and check-in.
5. Generate stages
Stages can be generated while the tournament is published or registration_closed, with at least
capacity.minEntrants confirmed enrollments. Generation seeds the confirmed entrants and creates the
stage's matches.
POST /tournaments/:slug/stages/generate (tournaments:mutate):
| Field | Meaning |
|---|---|
stageOrder | Position of the stage in the tournament, from 0. |
format | single_elimination, double_elimination, round_robin, swiss or free_for_all. |
configuration | Format options, for example pairingStrategy and thirdPlaceMatch (single elimination), grandFinalMode single_match or bracket_reset (double elimination), cycles 1–3 (round robin), rounds (Swiss). |
seeding | mode manual, snake or random; a seed string for reproducible random seeding; assignments of { enrollmentId, seed } for manual seeding. |
scoring | mode score, sets or rank; bestOf 3, 5 or 7; drawsAllowed; roundOverrides of { roundKey, bestOf } for, say, a best-of-five final. |
standingPolicy | Ranking overrides for formats that rank (not elimination brackets). |
expectedStageRevision | Optional guard when you regenerate an existing stage. |
Generating with the same inputs again returns the existing stage rather than a second one. A stage
cannot be changed once competitive play has started in it (409 STAGE_PLAY_STARTED).
POST /tournaments/:slug/stages/preview takes format, configuration and scoring and returns
the rounds those inputs would produce, without creating anything. It is a read (tournaments:read)
and needs no Idempotency-Key.
Group stage into a final stage
A stage composition creates a group stage and a final stage in one call and seeds the final from
the group qualifiers when the groups finish. POST /tournaments/:slug/stage-compositions takes
stageOrder, seeding, a groupStage (format, groupSize, advancePerGroup, scoring,
optional configuration and standingPolicy) and a finalStage (any format but free_for_all).
POST /tournaments/:slug/stage-compositions/preview returns the rounds of both stages without
creating them.
| Route | Scope | What it does |
|---|---|---|
GET /tournaments/:slug/stages | tournaments:read | Lists the active stages (cursor, limit). |
GET /stages/:stageId | tournaments:read | Reads one stage. |
PATCH /stages/:stageId | tournaments:mutate | Renames it (name). |
DELETE /stages/:stageId | tournaments:mutate | Archives it, before play has started in it. |
GET /tournaments/:slug/stage-compositions | tournaments:read | Lists compositions (cursor, limit). |
GET /tournaments/:slug/stage-compositions/:compositionId | tournaments:read | Reads one. |
GET /tournaments/:slug/matches | tournaments:read | Lists matches, optionally for one stageId (cursor, limit). |
A stage completes by itself when its last result is recorded, and reopens by itself if a
correction or revert makes it incomplete again. There is no call to start, complete or reopen a
stage; you receive tournament.stage.completed and tournament.stage.reopened events instead.
6. Start
POST /tournaments/:slug/start moves a registration_closed tournament to in_progress. It is
refused with 422 TOURNAMENT_NOT_READY unless:
- check-in close has been processed, when check-in is enabled;
- at least
capacity.minEntrantsenrollments are confirmed; - an active stage exists, with at least one active match.
Calling it on a tournament that is already in_progress returns it unchanged. With
lifecyclePolicy.autoStart, the same transition runs at schedule.startsAt.
7. Complete
POST /tournaments/:slug/complete moves an in_progress tournament to completed once every active
stage is complete; otherwise it answers 422 TOURNAMENT_NOT_READY. Completion finalizes the
placements and publishes tournament.tournament.completed and
tournament.tournament.placements-finalized, which is what triggers any awards bound to the
placements. With lifecyclePolicy.autoComplete and a schedule.endsAt, completion runs on its own
at that time.
8. Cancel and archive
POST /tournaments/:slug/cancel cancels a tournament in any state but completed, cancelled or
archived, and cancels its pending scheduled work and match forfeits.
DELETE /tournaments/:slug archives a tournament that is draft or cancelled: cancel a running
one first. An archived tournament drops out of GET /tournaments and its public projection answers
404.
Errors you will see
errorCode | Status | Meaning |
|---|---|---|
TOURNAMENT_NOT_FOUND | 404 | No tournament with that slug in your account. |
TOURNAMENT_STATE_INVALID | 409 or 422 | The transition or edit is not allowed in the current state. |
TOURNAMENT_NOT_READY | 422 | A precondition of reopen, start or complete is not met; message says which. |
TOURNAMENT_NOT_PUBLISHABLE | 422 | The configuration is incomplete. |
TOURNAMENT_CAPACITY_INVALID | 422 | A capacity change would invalidate active enrollments. |
TOURNAMENT_SLUG_CONFLICT | 409 | The requested slug is taken. |
TOURNAMENT_CONCURRENT_MODIFICATION | 409 | Another write won; read again and retry. |
STAGE_ENTRANTS_BELOW_MINIMUM | 422 | Not enough confirmed entrants to generate. |
STAGE_CAPACITY_UNBOUNDED | 422 | The tournament needs a finite capacity for stage generation. |
Tournaments
The Tournaments API for organizations, tournaments, stages, matches, teams, enrollments, free agents and standings, including the scopes that govern it.
Enrollment and check-in
Teams and rosters, enrollments and the waitlist, check-in and its close, free-agent requests, and marking a match ready.