TANITARDevelopers
GuidesTournaments

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"'"}'
RouteScopeWhat it does
GET /organizationsorganizations:readLists your organizations (cursor, limit).
GET /organizations/:organizationIdorganizations:readReads one.
PATCH /organizations/:organizationIdorganizations:mutateChanges name, slug or ownerPrincipalId.
POST /organizations/:organizationId/disableorganizations:mutateDisables it; its tournaments stop being reachable through ownership checks.
POST /organizations/:organizationId/enableorganizations:mutateRe-enables it.
DELETE /organizations/:organizationIdorganizations:mutateArchives 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.

FieldRequiredMeaning
organizationIdyesThe owning organization.
nameyesUp to 120 characters.
configurationModeyesexpress, standard or advanced.
visibilityyespublic or private. Only a public tournament answers GET /tournaments/:slug/public.
participantModeyessolo or team.
identityModesyesNon-empty set of saas and opaque.
capacityyesminEntrants (at least 2), and optionally maxParticipants, maxTeams, teamSize, maxSubstitutes. Stage generation needs a finite capacity.
registrationyesopensAt, closesAt (ISO 8601).
scheduleyesstartsAt, optional endsAt.
lifecyclePolicyyesautoStart, autoComplete, checkInReminderOffsetsMinutes (up to 10 distinct values, 1–10080) and matchForfeitTimeoutMinutes (1–10080 or null).
checkInnoenabled, opensAt, closesAt.
waitlistnoenabled, optional maxSize.
resultPolicynoevidenceRequired, drawsAllowed, rankBy and tiebreakers.
eligibility, customRulesnoFree-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

RouteScopeWhat it does
GET /tournamentstournaments:readLists the tournaments of the organizations you may act for, archived ones excluded (cursor, limit).
GET /tournaments/:slugtournaments:readReads one.
GET /tournaments/:slug/publictournaments:readThe public projection of a public, non-archived tournament: what you show players.
PATCH /tournaments/:slugtournaments:mutateUpdates 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):

FieldMeaning
stageOrderPosition of the stage in the tournament, from 0.
formatsingle_elimination, double_elimination, round_robin, swiss or free_for_all.
configurationFormat options, for example pairingStrategy and thirdPlaceMatch (single elimination), grandFinalMode single_match or bracket_reset (double elimination), cycles 1–3 (round robin), rounds (Swiss).
seedingmode manual, snake or random; a seed string for reproducible random seeding; assignments of { enrollmentId, seed } for manual seeding.
scoringmode score, sets or rank; bestOf 3, 5 or 7; drawsAllowed; roundOverrides of { roundKey, bestOf } for, say, a best-of-five final.
standingPolicyRanking overrides for formats that rank (not elimination brackets).
expectedStageRevisionOptional 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.

RouteScopeWhat it does
GET /tournaments/:slug/stagestournaments:readLists the active stages (cursor, limit).
GET /stages/:stageIdtournaments:readReads one stage.
PATCH /stages/:stageIdtournaments:mutateRenames it (name).
DELETE /stages/:stageIdtournaments:mutateArchives it, before play has started in it.
GET /tournaments/:slug/stage-compositionstournaments:readLists compositions (cursor, limit).
GET /tournaments/:slug/stage-compositions/:compositionIdtournaments:readReads one.
GET /tournaments/:slug/matchestournaments:readLists 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.minEntrants enrollments 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

errorCodeStatusMeaning
TOURNAMENT_NOT_FOUND404No tournament with that slug in your account.
TOURNAMENT_STATE_INVALID409 or 422The transition or edit is not allowed in the current state.
TOURNAMENT_NOT_READY422A precondition of reopen, start or complete is not met; message says which.
TOURNAMENT_NOT_PUBLISHABLE422The configuration is incomplete.
TOURNAMENT_CAPACITY_INVALID422A capacity change would invalidate active enrollments.
TOURNAMENT_SLUG_CONFLICT409The requested slug is taken.
TOURNAMENT_CONCURRENT_MODIFICATION409Another write won; read again and retry.
STAGE_ENTRANTS_BELOW_MINIMUM422Not enough confirmed entrants to generate.
STAGE_CAPACITY_UNBOUNDED422The tournament needs a finite capacity for stage generation.

On this page