Match results

The confirmed, server-computed outcome of every match your players play — the only signal you may move value on.

The call

GET /partner/results?cursor=<opaque>&limit=50 Authorization: Bearer <your API key> 200 { "rows": [ { "matchId": "m_7f3a…", "playerId": "u_8813", "gameId": "streakpeaks", "state": "ended", "outcome": "win", "endedAt": 1785000420, "tierId": "usd_10", "prizeUsdCents": 1000, "stakeUsdCents": 583, "feeUsdMicros": 250000, "played": true } ], "cursor": "…", "hasMore": false }

It authenticates with your API key, not a session token, and returns only that key’s environment’s rows.

limit counts matches, not rows

limit defaults to 50, capped at 200, bounding matches, not rows. A match produces one row per player of yours in it — a paid match seats two players of the same operator, so a decided one produces two rows.

limit=1 can return 2 rows; limit=200, up to 400. Size buffers on the row count, not limit.

The cursor is a position in the match history; a page always ends on a match boundary, never splitting one player's row from its opponent's. Read rows.length for what arrived and hasMore for whether to continue — never compare rows.length with limit to decide you’re done.

The three states

stateWhat happenedYour move
startedYour player is seated — on a paid match, at payment, not their first moveCommitted — take it once, if not already
endedThe match is over; outcome is finalCredit the result, once
no_contestNobody joinedReturn the entry, once

If you already took the entry when you seated the match, a started row is a confirmation, not a second charge — the one mistake on this feed that costs real money. The seating call moves the money (Paid matches step 4); this row only confirms it. Key your ledger on (matchId, playerId) and apply each movement once.

Neither a draw nor a no_contest carries a fee: a draw returns both players their entry in full, an unjoined match returns the one that was paid, and neither accrues. The model is on the integration page.

outcome is win, loss or draw, appearing only once the state is ended; it and endedAt are omitted entirely until then — never sent as null. Timestamps are Unix epoch seconds.

The money on a row

The last four fields appear on every partner-seated match — on started as well as ended, since debit the stake, once needs a number.

FieldWhat it is
tierIdWhich tier the seat was sold at.
prizeUsdCentsWhat the winner takes. Ours, fixed per tier.
stakeUsdCentsWhat you charged this player, sent on the seating call — for a priced tier, your account’s set price. We compute nothing from it; it matches your ledger to ours.
feeUsdMicrosWhat this seat costs you, under the agreed split, in micro-USD — millionths of a dollar: 10000 is one cent, 250000 is 25 c. Quoted before play, not after.

feeUsdMicros is always present; 0 and null differ. 0 means nothing is owed (a draw or a no_contest — you may close the accrual); null means we have not said. A falsy check collapses the two.

FieldWhat it is
playedWhether this player made a server-accepted move, present only on ended: true — they played; false — they did not (neither player opened reports false on both rows); absent — not recorded, not the same as false.
The only way to tell a no-show from a real tie on a paid match: two who paid and left report the same draw as two who tied. Branch on played, not outcome.

Each row carries only that seat’s money — two of your players meeting yields two rows, each with what was charged, which may differ.

Four rules that decide whether your ledger is right

  • Key every move on (matchId, playerId), never on matchId alone: two of your own players yield two rows sharing a matchId, and keying on the match alone drops one side's credit.
  • Expect rows to repeat. Once caught up, the cursor is held a few seconds behind the clock, so your next poll re-returns that window, catching any same-second result. Applying each (matchId, playerId) exactly once is therefore not optional.
  • You will often see ended without ever seeing started — a short match can begin and finish inside one poll interval. Debit and credit in one step; this is ordinary, not an edge case.
  • No row means nothing moved: a player who opens a game without starting a match never produces one, so there is no reservation to time out.

How long a match can wait

A match that never finds an opponent ends as no_contest after 72 hours — the longest your reservation ever stands. One with an opponent runs on its own clock, unaffected.

Paging, and catching up

Pass the cursor to get the next page; omit it to start from the beginning of your history. Follow hasMore — not whether rows came back non-empty.

Your first call has history behind it: omitting the cursor starts at the ACCOUNT’s history, not your integration’s, so a fresh build may re-pay months-old matches. On first start: page to the end, keep only the final cursor, settle what arrives after.

If a cursor is refused (400 invalid_cursor), stop and raise it — a valid cursor never expires, so refusal means corruption, truncation, or the wrong environment. Restarting re-reads the whole history, safe only if your ledger is keyed on (matchId, playerId).

An empty "rows": [] does not mean you are done, and it can happen mid-catch-up, two ways: a page can land entirely on matches with no row while hasMore stays true; or, when what’s left is an exact multiple of limit, the last full page still says hasMore: true before an empty one — neither is an error.

Store your last cursor to catch up after downtime. It’s opaque, valid only for its partner and environment; a foreign or edited one answers 400 invalid_cursor, never an empty page.

What a row does not contain

Almost nothing: a row carries only what’s tied to price, payout or identification. It omits the opponent’s uid, name, the deal, the seed and the move history — except on a paid match with both seats yours, where the two rows share a matchId and reveal who your player faced.

It also omits your own uid, either side’s score, and the engine version. Your uid is derivable from playerId: exactly ptr:<partnerId>:<playerId> (or ptr:test:<partnerId>:<playerId> in test) — the value session minting returned. Scores drive no payment: your player sees their own inside your frame; outcome plus played are what your ledger needs.

For a count of matches played rather than their outcomes, use the usage meter in the portal, not this endpoint.