The ticket lifecycle
For the "players join over time" case — building a lobby that fills up rather than starting from a known list. Four explicit steps, in order.
1. Create an empty ticket
POST /matchmaking/hosted/ticket?game=X — starts a forming ticket with nobody in it yet. Returns a ticketId.
2. Add players as they connect
POST /matchmaking/hosted/ticket/:id/players?game=X, body { playerId } — one call per player, whenever they show up. Fails with 409 if that player isn't currently connected, or the ticket's already full.
3. Promote when you say it's ready
POST /matchmaking/hosted/ticket/:id/promote?game=X — there is no automatic promotion. No size threshold, no timer. The ticket only becomes visible in the pool once your backend explicitly promotes it.
4. Start the match
POST /matchmaking/hosted/start-match?game=X, body { groupIds: [...] } — fires the match immediately from one or more promoted groups. No countdown; your call to this endpoint is the decision.
POST /ticket → { ticketId }
POST /ticket/:id/players → { result: "ok" } (repeat per player)
POST /ticket/:id/promote → { group } (now visible via GET /pool)
POST /start-match → match begins