Skip to main content
Matchmaking pairs two different players within your app. Your app owns the game, results, payments, and resolution; none of those are matchmaking inputs.

Setup

Use this client on your server. Your exact origin must be Bankroll Verified and serve a signed manifest containing your appKey. No matchmaking capability is needed.
Take player from a verified session on your server. App authentication does not authenticate a player field submitted by a browser. Authorize each user’s access before returning tickets: app credentials can discover the whole app.

Model

Payloads contain shared game conditions, such as a seed or target. Keep results and private data in your app. A player may have multiple tickets, but two tickets with the same player never pair.

API

Types are exported from @joinbankroll/sdk/matchmaking:
All times are epoch milliseconds. IDs and player identifiers are nonempty UTF-8 strings up to 256 bytes; queue keys allow 512 bytes. Payloads allow 16 KiB of JSON. Numbers must be finite. Omit optional fields rather than setting undefined; Date, BigInt, functions, sparse arrays, and cyclic objects are not JSON inputs.

Create and play

Build the input from the app action you already recorded and the verified session. Retain the original fields for retries, including any rating or cutoff.
New admissions attempt pairing with the oldest compatible waiting ticket. Retries and listing return state; they do not attempt new pairings. Play only after admission, using the accepted payload. Record whether the round was played in your app so polling or crash recovery cannot replay it. A matched ticket stays matched after either player finishes or fails to play; your app resolves the outcome.

Guarantees

  • Idempotent creation. Identical input returns the ticket’s current state. JSON object key order does not matter. Changing any field for an admitted ID returns ticket_conflict, including after it becomes terminal.
  • Fixed queue policy. The first accepted input fixes the queue’s size and rating policy, even if that ticket is already expired. Later disagreement returns queue_conflict. Use a new key to change policy. Only size 2 is supported.
  • Final matching. A ticket belongs to at most one match. Both tickets return the same match; neither returns to the pool.
  • Atomic cancellation. cancelTicket returns matched if pairing won, or cancelled if cancellation won. It never returns waiting. Retrying returns the same terminal outcome.
  • Cancellation before creation. Cancelling an unknown ID records a permanent cancellation with admission: null. A delayed creation for that ID returns it without admission, regardless of the proposed fields.
  • No default expiry. Omit expiresAt to wait indefinitely. A waiting ticket at or past its app-selected cutoff becomes cancelled with reason expired when read, considered for pairing, or cancelled. It cannot match past the cutoff. A past cutoff on creation cancels immediately; a completed match never expires.
  • Request-driven progress. There are no background timers or automatic rematches. Reads evaluate expiry; new admissions attempt matching. Your app decides when to poll, cancel, or resolve a non-playing opponent.
  • Discovery with admission. Every accepted ticket is discoverable in its app as soon as it can match. Terminal tickets and matches remain discoverable; there is no deletion or ID-reuse operation.

Ratings

A rated ticket requires a queue rating policy. initial must be nonnegative, widenPerSecond positive, and optional max at least initial. All are finite. At admission time, each waiting candidate’s tolerance is:
Omitting max leaves the band uncapped. waitingSeconds is the older ticket’s elapsed wait; the joining ticket contributes no wait. The oldest compatible candidate wins. Rated tickets do not pair with unrated tickets. When neither has a rating, pairing is FIFO among different players.

Recover a lost ID or reply

listTickets() discovers all of your app’s tickets, including matched and cancelled ones. It does not require a queue, player, or ticket ID:
Optional player and id filters combine. Keep the same filters on subsequent pages and pass the cursor unchanged. Pages contain at most 25 tickets in creation order. A traversal covers tickets present when its first page began; states are current when each page is read. Start another traversal for later admissions or state changes. Unknown IDs return an empty page, not a cancellation. Cancelling an unknown ID has no player association, so find that cancellation by ID or app-wide listing. A player filter cannot find it. This removes the lost-ID orphan: admission and discovery are one guarantee, including if a crash loses the response or pairing finishes before recovery. Even a lost player ID is recoverable through app-wide listing. Using an existing app action’s ID also makes retries and reconciliation straightforward. Recovery requires access to the same verified app identity; it cannot make an abandoned app return or a player finish a round.

Lifecycle and caller flow

Terminal records remain discoverable.

Errors

MatchmakingError exposes code and status (HTTP status, or null for a local failure). A body-read timeout can preserve HTTP 200; handle the error by code. Calls time out after 30 seconds and are never automatically retried. A timeout or missing response is not proof that creation or cancellation failed. Do not invent a replacement ID to retry the same action.

HTTP

Other stacks use POST https://api.joinbankroll.com/api/matchmaking with Content-Type: application/json and app authentication. The successful response is the ticket or page directly. Refusals return { "error": "<code>" }; service unavailability returns 503. invalid_response is an SDK error, not an API response code.