Paid matches

Your player picks a tier, your page confirms and charges them, and your server seats the match. You collect the entry and pay your own winner; we supply the game, decide the result, and bill a fee for the seat. A paid match pairs two of your own players.

How it starts

  1. Your player taps a tier card, priced with your number — see Managing your account.
  2. The frame posts to your page: { type: "seatRequest", nonce, gameId, tierId, attempt }. It carries no amount — price from your own copy of the ladder, never a browser.
  3. You run your own confirm dialog: your price, your currency, your balance.
  4. Your server debits the player and calls the seating endpoint below.
  5. The frame enters the match on its own, through our API; it does not wait for an answer from your page, and no message from it can seat anybody.

The confirm step is required, not advisory — we place no limit on seat requests; your dialog is what stands between repeated taps and repeated charges.

Derive your idempotencyKey from attempt: same for that player, game and tier across a double tap or reload, so those collapse onto one charge and one seat.

Who plays whom

Today a paid match seats two players of the same operator — when nobody is waiting, they wait for another of your players at that game and tier, not one of ours or another platform’s. A tier nobody else on your platform is playing does not fill.

What the numbers are

  • The prize is ours, fixed per tier — what your winner takes. You do not set it.
  • The entry is yours to set, from the tier’s floor to its prize — below is refused, and so is above the prize. Anything above the floor you keep; it never reaches us.
    You set it per tier in the portal, the number your player sees on the card. The seating call must send that same number — a seat that disagrees is refused.
  • The fee is per seat, our share of a fixed base under the split agreed with your account — quoted before the match and pinned to the seat, so renegotiating a split does not restate an earlier one.

The tier cards do not appear unless your handshake asks for them — your init message must carry seating: "host". Leave it out and the embed has practice and no match entry — see Embedding a game.

Read the ladder first

GET /partner/tiers?gameId=streakpeaks Authorization: Bearer <your API key> 200 { "tiers": [ { "tierId": "usd_1", "prizeUsdCents": 100, "minStakeUsdCents": 55, "entryUsdCents": 55 }, { "tierId": "usd_20", "prizeUsdCents": 2000, "minStakeUsdCents": 1100, "entryUsdCents": 1400 } ], "pricesUpdatedAt": 1760000000 }

minStakeUsdCents is the floor your price must clear; the prize is the ceiling. Read this — don’t hard-code it.

entryUsdCents is what YOUR player is charged — your Admin’s portal price, or our floor if none. Price your confirm dialog from this. Read it per launch, not hard-coded: a stale figure is refused at seat time.

pricesUpdatedAt is when your Admin last saved the price map, in epoch seconds, or null if nobody has — a real answer, not missing. Cache the ladder and compare this to know your copy is current.

It is a live read, not a start-up setting to cache — an Admin can change the price at any moment and this always answers current. minStakeUsdCents is different: ours, the least the seating call accepts.

The card your player sees follows within 60 seconds, not instantly — the same propagation described for origins on Embedding a game. Read it per launch, and for a promotion, change the price before the traffic rather than during it.

The ladder is the tiers you can seat. A tier we cannot offer is simply absent, not listed and flagged. Read it at start-up, cache it, and refresh — don’t hard-code the list.

Seat the match

stakeUsdCents must match the price we published. Read it from entryUsdCents, not a constant, so a portal price change cannot leave your backend behind. Errors and limits gives the rule, the stake_does_not_match_price code, and how an unpriced tier is handled.

POST /partner/matches Authorization: Bearer <your API key> { "playerId": "u_8813", "gameId": "streakpeaks", "tierId": "usd_20", "stakeUsdCents": 1400, "idempotencyKey": "your-own-id-for-this-request" } 200 { "matchId": "m_7f3a…", "uid": "ptr:acme:u_8813", "joined": false }

joined: false means seated and waiting for an opponent — another of your own players, as above. true means one was already waiting and play is under way. Either way the seat exists and the match id is yours.

Seating a player who is not already in the frame

Seating from somewhere else — a lobby, a promotion, an email — does not put anyone in front of the match. Mint a session, embed the frame as on Embedding a game, and deep-link it on init:

frame.postMessage({ type: "init", token, // from POST /partner/sessions, same gameId uid, // the uid POST /partner/matches returned dealerUrl: "https://api.peritusgames.com", gameId: "streakpeaks", route: "/play/" + matchId, }, GAME_ORIGIN)

Send route to land your player straight in the match; without it they land in the lobby, where the seat is waiting — sorted above finished matches in their list, and counted on the Matches tab.

The idempotency key, and the one answer that lies

idempotencyKey is yours to choose — 1 to 128 characters of A-Z a-z 0-9 _ - . The same key for the same request returns the same seat; for a different request it returns 409 idempotency_key_conflict instead. Errors and limits gives the rule and the code.

A 503 does not mean the seat was not taken — the answer can be lost after we have already seated, so settlement_unavailable means only that we could not tell you. Retry with the same key: you get the existing seat back, or a new one. A fresh key opens a second paid match beside the one you already have.

When nobody joins

A match that expires without an opponent is no_contest, billed a stated zero. Return the entry to your player.

A player who paid but did not play is different: ended, with played: false — no refund; the result stands by score.

What happens next

Match results carries the tier, prize, your stake echoed back, and the fee — from the started row, not just the end. A draw and a no_contest are both billed a stated zero. Every refusal this page can produce is catalogued under Errors and limits.