---
title: "Update a tournament"
method: POST
path: "/tournaments/{identifier}/update"
tags: ["Tournament: Lifecycle"]
---

# Update a tournament

`POST /tournaments/{identifier}/update`

Applies a partial edit to a tournament, resolved by slug. The result is TRI-STATE and you must branch on `applied`: before the tournament has ever been approved the edit applies directly (`direct`); afterwards a material change is staged into a pending set and the tournament flips to AWAITING_APPROVAL (`pending`), and an edit that changes nothing is a `noop`. A COMPLETED or CANCELLED tournament rejects every edit with 400. Requires an API key holding the tournaments lifecycle capability whose owner is a tournament ADMIN - a tournamentStaff MODERATOR is NOT sufficient and receives 403.

## Path parameters

- `identifier` string, required — Tournament slug.

## Request body

- TournamentUpdateRequestBody — Partial tournament edit. Send only the fields you are changing; at least one recognised field is required. Fields this schema does not list are IGNORED rather than rejected, so a misspelled key silently does nothing - check the response `applied` value and `changedFields`. `freeAgentsEnabled`, `seedingMethod` and the league-points settings are NOT editable here (league points has its own endpoint).
  - `name` string
  - `description` string
  - `rules` string
  - `gameId` string — Convex game id.
  - `playlistId` string — Convex game playlist id.
  - `teamSize` integer
  - `format` 'SINGLE_ELIMINATION' | 'DOUBLE_ELIMINATION' | 'ROUND_ROBIN' | 'SWISS'
  - `formatConfig` TournamentFormatConfig — Format-specific configuration. Sent whole - it replaces the stored object rather than merging into it.
    - `bestOfLadder` object, required — Best-of ladder. Both fields are required when formatConfig is sent.
      - `default` integer, required — Default best-of for every round.
      - `overrides` object, required — Per-round best-of overrides, keyed by round key (e.g. `GF`, `R1`).
    - `grandFinalAdvantage` object, required
      - `type` 'NONE' | 'WIN_HEADSTART' | 'MAP_SELECTION' | 'BRACKET_RESET', required
      - `winHeadstart` integer — Maps the upper-bracket finalist starts ahead by. WIN_HEADSTART only.
    - `swissRoundCount` integer — Swiss only. Number of rounds to play.
    - `roundRobinPasses` union — Round-robin only. 1 = single pass, 2 = double pass.
      - 1
      - 2
  - `minParticipants` integer
  - `maxParticipants` integer — Re-checked against the format entrant ceiling AND the host plan's participant limit whenever it or `format` changes.
  - `registrationMode` 'OPEN' | 'APPLICATION' | 'INVITE_ONLY'
  - `registrationStartsAt` integer — Unix ms.
  - `registrationEndsAt` integer — Unix ms.
  - `checkInRequired` boolean
  - `checkInOpensMinutesBefore` integer
  - `startsAt` integer — Unix ms.
  - `endsAt` integer — Unix ms.
  - `prizePool` number
  - `prizeCurrency` string
  - `prizeBreakdown` TournamentPrizeBreakdownEntry[]
    - `placement` integer, required
    - `amount` number
    - `description` string
  - `prizeDescription` string
  - `bannerStorageId` string — Convex storage id from POST /uploads/image with slot=banner (max 2 MB). An id minted by POST /uploads/image-url carries no ownership record and is rejected with 400 error_image_blob_unbound.
  - `logoStorageId` string — Convex storage id from POST /uploads/image with slot=avatar (max 1 MB). An id minted by POST /uploads/image-url carries no ownership record and is rejected with 400 error_image_blob_unbound.
  - `socialLinks` TournamentSocialLinks — Organizer social links.
    - `website` string
    - `x` string
    - `discord` string
    - `youtube` string
    - `twitch` string
    - `instagram` string
    - `tiktok` string
  - `sponsorTags` TournamentSponsorTag[]
    - `prefix` 'POWERED_BY' | 'SPONSORED_BY' | 'PRESENTED_BY' | 'CUSTOM', required
    - `customPrefix` string — Used only when prefix is CUSTOM.
    - `name` string, required
    - `url` string
    - `logoStorageId` string — Convex storage id for the sponsor logo, from POST /uploads/image with slot=avatar (max 1 MB). An id minted by POST /uploads/image-url is rejected with 400 error_image_blob_unbound. On an update, re-send the id already stored on each sponsor row you are not changing.
  - `visibility` 'PUBLIC' | 'UNLISTED'

## Response `200`

The edit was accepted. Branch on `applied`.

- TournamentUpdateResponse — Tri-state result of a tournament edit.
  - `applied` 'direct' | 'pending' | 'noop', required — `direct` - the tournament has never been approved, so the edit is live immediately. `pending` - the tournament has been approved before, so the edit was staged in `pendingChanges` and the approval status flipped to AWAITING_APPROVAL; it is NOT live and the tournament is off the public listing until staff re-approve. `noop` - the tournament has been approved before and every supplied value already matched the stored one, so nothing was staged and the approval status was left alone.
  - `changedFields` TournamentMaterialField[] — Present only when `applied` is `pending`. The material fields staged for re-approval, which is the merge of this request with any changes already pending.
  - `approvalStatus` 'NOT_SUBMITTED' | 'AWAITING_APPROVAL' | 'APPROVED' | 'REJECTED' — Present only when `applied` is `pending`, where it is always AWAITING_APPROVAL. Omitted otherwise, because the `direct` and `noop` arms leave the approval status untouched and echoing a pre-request snapshot would be a guess.
  - `timestamp` string, required

## Other responses

- `400` — Bad request (invalid body, cursor, limit, or date).
- `401` — Missing or invalid API key.
- `403` — API key lacks the required permission.
- `404` — Resource not found.
- `429` — Rate limited.
- `500` — Internal server error.

---

[API](https://skmtc.net/teambattles/apis/teambattles-api.md) · [All operations](https://skmtc.net/teambattles/apis/teambattles-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/teambattles/teambattles-api/revisions/2ffe8fb53b7d/schema)
