Payouts
Pay cash prizes from your own Stripe Connect account to your players' connected accounts, without Tanitar ever holding the funds.
A payout sends the cash value of a prize award to the player who won it. The money moves
from your Stripe Connect Standard account to the player's connected Stripe account, as a Stripe
transfer made on your account's behalf. Tanitar never holds, pools or forwards the funds: it
decides when a payout is due, submits it once, and records what Stripe reports.
Before you start
Payout configuration is done in the dashboard by an administrator of your organization, not with an API key. See Payouts in the dashboard. There you:
- Connect your Stripe account (or create one) and complete Stripe's onboarding.
- Choose the currencies you allow for payouts. A payout is made only in your account's default currency, and only if it is on that list.
- Register each winning player's connected Stripe account (
acct_…) as a payout recipient. A recipient must beeligibleto be paid.
Then, on the API side:
- Check that payouts are available:
GET /v1/payouts/capabilitiesreturns{ "providers": ["stripe"] }when the deployment's Stripe preflight passed, and{ "providers": [] }otherwise. - Create a
prizeaward definition with apayoutofcurrency(three lowercase letters, such aseur) andamountMinor(an integer in the currency's minor unit), bound to a tournament and a rank. See Award kinds and awards.
When the tournament completes, the winner receives the prize award automatically.
Request a payout
POST /v1/awards/:awardId/payouts with scope rewards:mutate and no body.
curl -X POST "$REWARD_API_URL/v1/awards/awd_91c2/payouts" \
-H "Authorization: Bearer $TANITAR_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"There is exactly one payout per award. Requesting it again, with any key, returns the same record.
The request is checked before anything reaches Stripe:
| Answer | Cause |
|---|---|
503 PAYOUT_DISABLED | Payouts are not enabled on this deployment |
404 AWARD_NOT_FOUND | No such award in your tenant |
422 PAYOUT_KIND_UNSUPPORTED | The award is not an applied prize held by a player, or its definition has no payout |
422 PAYOUT_AMOUNT_INVALID | The definition's amount is not a positive integer |
503 PAYOUT_CONFIGURATION_UNAVAILABLE | Your payout configuration could not be read; nothing was recorded, retry later |
If the request is admissible but your configuration does not allow it, the payout is recorded as
rejected with a failureCode, and Stripe is never called:
failureCode | Cause |
|---|---|
PAYOUT_ACCOUNT_INACTIVE | Your Stripe account is not connected, onboarded and active |
PAYOUT_RECIPIENT_INELIGIBLE | The player has no eligible connected account registered |
PAYOUT_CURRENCY_UNSUPPORTED | The prize currency is not your account's default currency or not on your allowlist |
Otherwise the payout is claimed and Tanitar submits it in the background.
Statuses
| Status | Meaning |
|---|---|
claimed | Accepted and waiting for its next attempt |
submitted | Sent to Stripe under a stored idempotency key; the answer is not yet recorded |
settled | Stripe made the transfer; transferId is Stripe's transfer id |
reversed | The transfer was settled and later reversed, fully or partly (amountReversed) |
failed | Every attempt failed, or Stripe refused the transfer |
rejected | Refused before any call to Stripe (see failureCode) |
unresolved | Stripe's answer was lost; the next reconcile finds out what happened |
A payout is attempted up to six times, on a fixed ladder: immediately, then after 30 seconds, 2 minutes, 10 minutes, 30 minutes and 2 hours. Each attempt reuses a key stored before the call, so Stripe never executes the same attempt twice, and an attempt whose answer was lost is checked with Stripe before any new one is made.
Read and reconcile
| Route | Scope | Returns |
|---|---|---|
GET /v1/payouts | rewards:read | Every payout in your tenant |
GET /v1/awards/:awardId/payouts | rewards:read | The payout of one award |
POST /v1/payouts/:payoutId/reconcile | rewards:mutate | The payout after asking Stripe for its current state |
Neither list is paginated or filtered. Each payout reads as:
{
"payoutId": "pay_5d0e…",
"awardId": "awd_91c2",
"status": "settled",
"currency": "eur",
"amountMinor": 5000,
"transferId": "tr_1Q…",
"settledAt": "2026-09-30T12:40:03.000Z",
"playerId": "player-7f3a",
"failureCode": null,
"amountReversed": 0,
"attemptCount": 1
}Reconcile reads the transfer from Stripe and records it: a transfer found settles the payout (or
marks it reversed), and a transfer proven absent lets the next attempt proceed. If Stripe cannot
be reached, reconcile answers 503 PAYOUT_PROVIDER_UNAVAILABLE and changes nothing. A rejected
payout is returned unchanged.
No response contains a Stripe account id, an idempotency key or a credential.
What your players see
A player reads their own payouts with GET /player/v1/payouts: the award, status, currency, amount,
Stripe transfer id and settlement time, without failure details.
When a payout settles, fails or is reversed, reward.payout-settled is published and can be
delivered to your webhook endpoint. See the event catalog.