v20

OpenAPI 3.1.0raw.githubusercontent.com2026-08-011984401.2 MB
Tournament: Lifecycle

Create a tournament

Creates a tournament as a DRAFT with approval status NOT_SUBMITTED. Nothing is public yet: call submit-for-approval, then open registration once staff approve.

HOST AUTHORITY. hostType and hostId are yours to choose, but not to invent - you must actually hold the authority you name, or the request is refused. user requires hostId to be your own user id (otherwise 403 error_forbidden); org requires an active OWNER or ADMIN membership of that organization (otherwise 403 error_not_org_admin); league requires an active league ADMIN or OWNER membership (otherwise 403 error_league_admin_required). You must also be eligible to host at all - a paid plan, or an account at least 30 days old, and not competitively banned - or the request is 403 error_not_eligible_to_host.

BILLING. Creation is billed to YOU, the key owner, because there is no tournament yet to resolve a host from. From this point on the billing flips: every organizer request against the tournament is billed to the HOST - the organization owner or the league owner - including requests made by tournament staff you add later. Choosing a host is therefore the act that binds that person's developer plan, which is exactly why the authority check above is strict.

Unlike the rest of the tournament family this endpoint takes no identifier, so it is caller-billed and requires only the tournaments lifecycle capability on the key plus the tournamentOrganizerApi feature on your own plan.

Supply an optional Idempotency-Key request header to make retries safe: an identical key and body replays the original tournament id and slug without creating a second tournament, while the same key with a different body returns 409 error_idempotency_key_conflict. Without the header a retried request creates a SECOND tournament, because a tournament has no natural key to collapse on.

post/tournaments

Request body

hostType'user' | 'org' | 'league' required

Who hosts the tournament. This choice, together with hostId, decides which plan is billed for every later organizer request on the tournament - see the endpoint description.

hostIdstring required

Your own user id when hostType is user; the organization id when org; the league id when league. You must genuinely hold that authority - see the endpoint description.

namestring required

Tournament name, 3-80 characters after trimming.

descriptionstring
rulesstring
gameIdstring required

Convex game id.

playlistIdstring

Convex game playlist id.

teamSizeinteger required

Players per side.

format'SINGLE_ELIMINATION' | 'DOUBLE_ELIMINATION' | 'ROUND_ROBIN' | 'SWISS' required

Bracket format of a tournament.

minParticipantsinteger required
maxParticipantsinteger required

Entrant cap. Also bounded by the format ceiling (SINGLE_ELIMINATION and DOUBLE_ELIMINATION 128, SWISS 64, ROUND_ROBIN 32) and by the host plan's participant limit, both checked server-side.

registrationMode'OPEN' | 'APPLICATION' | 'INVITE_ONLY' required
registrationStartsAtinteger

Unix ms.

registrationEndsAtinteger

Unix ms.

checkInRequiredboolean required
checkInOpensMinutesBeforeinteger
freeAgentsEnabledboolean required

Whether unaffiliated players may join a free-agent pool. NOT editable afterwards through the update endpoint.

seedingMethod'MANUAL' | 'RANDOM' | 'REGISTRATION'

Defaults to the platform default when omitted. NOT editable afterwards.

startsAtinteger

Unix ms.

endsAtinteger

Unix ms.

prizePoolnumber
prizeCurrencystring
prizeDescriptionstring
bannerStorageIdstring

Convex storage id.

logoStorageIdstring

Convex storage id.

visibility'PUBLIC' | 'UNLISTED' required
leaguePointsEnabledboolean

LEAGUE HOSTS ONLY. Sending it with a user or org host is a 400.

Response

The draft tournament was created.

successtrue required
tournamentIdstring required
slugstring required

Derived from the name and de-duplicated server-side, so it is NOT predictable from the name you sent. Every other tournament endpoint resolves by this slug, never by tournamentId, so store it.

status'DRAFT' required

Always DRAFT. A guaranteed post-condition, not an echo - createCore hard-codes it.

approvalStatus'NOT_SUBMITTED' required

Always NOT_SUBMITTED. Call submit-for-approval next; the tournament is invisible to the public discovery surface until staff approve it.

timestampstring required