Skip to main content

Promotion Races — Product Guide for Site Operators

A Race is a time-boxed leaderboard competition layered on top of real-money play. Players compete on a scoring metric (total wagered, number of bets, or biggest win multiplier) over a fixed window, and the top finishers split a prize pool. Today, "Race" is the only kind of Promotion the platform supports — in the backoffice and API you'll see kind: "race" on every promotion.


Races are built and managed in the backoffice (under Incentives → Races), and displayed to players via the public API, which your own site frontend calls directly. This guide covers both halves: how to configure a race as an operator, and how to build a race experience for your players.


​Race Lifecycle & Statuses

A race moves through four states. Status is computed live from the current time vs. the race's start/end dates — it's not something you set manually:

  • Scheduled — start date is in the future. Fully editable. Deletable.

  • Active — current time is between start and end. Standings update continuously. Deletable option disappears; certain settings lock (see below).

  • Completed — end date has passed. Standings are frozen for display, but nothing has been paid out yet. You can no longer edit the race at all.

  • Finalized — you've explicitly confirmed the results ("Complete Race"). This is a manual step, not automatic — a completed race sits and waits for you to finalize it. Once finalized, standings and configuration become permanently locked, and prize claims are created for each winning rank.

Two useful constraints to plan around:

  • A new race's start date can't be in the past, and once scheduled, you can push the start date later but never earlier.

  • Once a race goes Active, the settings that affect scoring (race type, products, bankroll/token, minimum bet size, odds range, RTP cap, exclude toggles, referral gating) are locked — changing them mid-race would make standings computed before and after the change inconsistent. The one exception: you can always extend the end date while the race is still running. If you need different rules, let the current race finish (or delete it while still scheduled) and create a new one.

Configuring a Race in the Backoffice

Navigate to Incentives → Races → New Race. The form has three sections.​

Basic Information

  • Race Title — the player-facing name.

  • Slug — used in the race's URL; auto-generated from the title until you edit it manually.

  • Category / Category Name — grouping/filtering metadata (defaults to race / Race).

  • Image URL / Icon URL — visual assets for race cards and detail pages.

  • Start Date and Time / End Date and Time — the race window. See lifecycle notes above for edit constraints.

Content

  • Race Description — main player-facing copy.

  • Scheduled Message (optional) — shown while the race is still scheduled, before it opens.

  • Section Title / Section Content — a secondary content block, e.g. rules or terms (defaults to a "Rules" heading).

Race Configuration — this is where the scoring logic lives:

  • Site Bankroll — which wallet balance/currency pool a bet must be placed against to count. This also determines the payout currency: the Token field is auto-filled and read-only based on your bankroll choice. If you only run one bankroll, you can mostly ignore this — it defaults to your first available bankroll and there's nothing further to configure. Only relevant if you run multiple currencies/bankrolls.

  • Referral code (only visible if you have referral permissions) — restricts the race to players who signed up through a specific referral/affiliate code. Leave blank for an open race.

  • Race Type — the scoring metric:

    • Wagering — ranks by total amount staked.

    • Bets — ranks by number of qualifying bets placed.

    • Highest Multiplier — ranks by the single biggest win multiplier hit on any qualifying bet (great for casino "big win" races). This is the only type that uses Minimum Multiplier.

  • Products — which verticals count: Casino, Sportsbook, or both. This gates which fields below even apply.

  • Minimum Bet Size — the smallest stake that counts toward the race, regardless of product. Defaults to 0 (no floor).

  • Minimum Multiplier — only shown for Highest Multiplier races; the floor a bet's multiplier must clear to be considered.

  • Minimum Odds / Maximum Odds — sportsbook-only. Filters bets by their effective odds (potential win ÷ stake, not the raw combined odds of a system bet). Default minimum is 1.4 — chosen deliberately higher than the typical 1.5/-200 convention, to stop wagering targets being cleared risk-free on heavy favorites. Leave maximum blank for no ceiling.

  • Maximum Return to Player — casino-only. Caps eligible games to those at or below a given RTP (e.g. 97%), so the race steers play toward house-edge games rather than subsidizing near-even-money titles. Must be below 100%. Leave blank for no cap.

  • Exclude open bets — when on, only settled/closed bets count; in-flight stakes don't count until they resolve. Applies to both products.

  • Exclude cashed out bets — sportsbook-only. When on, bets that were cashed out before full settlement don't count. Note: cancelled and refunded bets never count, regardless of this toggle.

  • Prize Pool — the total amount being distributed, denominated in the race's token.

  • Prize Distribution — add a row per paid rank with a percentage of the pool; percentages must sum to exactly 100%. Add or remove ranks freely (at least one required).

  • Show user rank in leaderboard + Text for unranked users — controls whether players outside the paid/visible ranks see their literal position or a placeholder ("Not ranked yet" by default). Useful if you'd rather not show low ranks to a long tail of participants.

How Bet Qualification Actually Works

Useful to understand so you can explain to a player (or support agent) why a specific bet didn't count. A bet is checked against these gates in order — failing any one disqualifies it:

  1. Product matches the race's selected Products.

  2. The bet's balance is on the race's configured Site Bankroll.

  3. If Exclude open bets is on, the bet must already be settled.

  4. The bet falls within the race's start/end window.

  5. The stake meets Minimum Bet Size.

  6. If a Referral code is set, the player was referred through it.

  7. For Highest Multiplier races, the bet's multiplier meets Minimum Multiplier.

  8. Casino only: the game's RTP is at or below Maximum Return to Player, if set.

  9. Sportsbook only: cancelled/refunded bets are always excluded; cashed-out bets are excluded only if that toggle is on; effective odds must fall within Minimum/Maximum Odds, if set.

Leaderboards: How Fresh Is the Data?

Standings are maintained by a background engine that watches bets as they happen and keeps a running tally per player, rather than recalculating from scratch on every page load — this keeps leaderboards fast even for large or long-running races. On top of that, responses pass through caching at two layers, so it's worth knowing the real-world lag at each endpoint:

  • Race standings (/v1/leaderboards/races/:id) and the leaderboard-discovery endpoint are cached for up to 60 seconds both in the application layer and, in production, at the CDN edge — so a player's displayed rank can lag their actual play by up to a minute.

  • Promotion data (/v1/promotions, /v1/promotions/:id) is excluded from CDN caching, since responses are personalized per logged-in player, but still carries the same ~60 second application-level cache.

  • Qualified bets (/v1/leaderboards/races/:id/qualified-bets) carries no application-level cache, but in production it is still cached at the CDN edge for up to ~5 seconds. That's the shortest window of any race-related endpoint, making it the closest thing to a live feed — just not instantaneous.

  • If the background standings engine ever falls behind, the platform automatically and invisibly falls back to a slower, always-correct live calculation — standings are never wrong, just occasionally a little slower to update. No operator action needed either way.

Finalizing a Race & Paying Winners

Once a race is Completed, open its detail page:

  1. Click Complete Race and confirm ("I Agree"). This locks the final standings permanently and creates a prize claim for every rank with a non-zero payout. This step is manual and one-way — there's no un-finalizing.

  2. Pay winners either individually (Pay on a single row) or in bulk (Pay Winners). Each payment debits your incentive wallet and is tracked through a status: Unpaid → Processing → Paid (or Failed/Review, with automatic retry on insufficient funds).

  3. A race with zero qualifying players can still be finalized — it simply produces no prize claims.

Note: today, the backoffice does not include a bet-level "qualified bets" browser for admins — that data is exposed via the public API instead, described next.


​Public API: Building Races Into Your Site

This is the API your own site frontend calls directly to render race pages for players. A few things to know before integrating:

  • No API key. The API identifies which site is calling based on the hostname the request arrives on — it's designed to be called from your site's own frontend, not as a server-to-server integration with a secret.

  • Personalization is session-based. The promotions endpoints read a login session cookie to return the calling player's own standing; anonymous visitors just get null there. The leaderboard and qualified-bets endpoints are fully public reads — anyone can view current standings without being logged in, like a public scoreboard.

  • Some backoffice-configured fields aren't exposed publicly: the referral code gate, the RTP cap, and the finalized timestamp are all enforced server-side but do not appear in the public race object today. If your rules copy needs to mention an RTP cap or referral requirement, source that text from your own configuration rather than expecting the API to return it.

Endpoints

  • GET /v1/promotions — list promotions. Filter with kind=race, category, status (scheduled/active/completed), paginate with limit (max 200) and nextCursor/prevCursor. Each item includes the core promotion fields plus an embedded race object with its rules (products, minBetSize, odds range, excludeOpenBets/excludeCashedOut, prizePool, payouts, rank-visibility content). Good for a "current races" list page.

  • GET /v1/promotions/:promotionId — a single promotion, always with its full race config plus a player field containing the calling player's own leaderboard row (or null). This is the endpoint for a race's own detail/landing page.

  • GET /v1/leaderboards — discovery endpoint listing which leaderboard types exist for your site (e.g. ["volume","points","races"]); "races" only appears if you have at least one race.

  • GET /v1/leaderboards/races/:promotionRaceId — the full current standings for one race, returned as a single ranked array (position = array order, not paginated). Each entry has account (id, nickname), prize, and a metric field matching the race type — wagered, bets, or highestMultiplier. Use the race's content.isRankVisible/unrankedText (from the promotions endpoints) to decide how to render players outside the paid ranks.

  • GET /v1/leaderboards/races/:promotionRaceId/qualified-bets — a cursor-paginated feed of individual bets that counted toward the race, newest first (cursor, limit 1–100, default 25). Cached for only ~5 seconds at the edge in production, making it the freshest of the race-related endpoints. Each entry has the qualifying bet's id, the player's account (id, nickname), wagered, multiplier, createdAt, and product-specific detail — game/studio info for casino bets, or betslip and individual bet-leg detail (sport, market, selection, odds) for sportsbook bets. This is ideal for a live "recent qualifying bets" ticker on a race page.

Suggested integration flow

  1. Fetch /v1/promotions?kind=race&status=active (and scheduled/completed as needed) to populate a races list/hub page.

  2. On a race page, fetch /v1/promotions/:promotionId to render the hero, rules copy, and the player's own rank if logged in. (Our own whitelabel menu already uses this same call to show a player's current rank as a nav entry — only relevant to you if you're building a fully custom frontend instead of using the provided whitelabel menu.)

  3. Poll /v1/leaderboards/races/:promotionRaceId every 30–60 seconds (matching the cache window) to keep the visible leaderboard current.

  4. Optionally poll /v1/leaderboards/races/:promotionRaceId/qualified-bets for a live activity ticker.

  5. Drive your rules/terms text off the config fields returned in race (products, minBetSize, minOdds/maxOdds, excludeOpenBets, excludeCashedOut, prizePool, payouts) rather than hardcoding, so copy stays correct if you adjust the race later — remembering that referral gating and RTP cap aren't in that payload and would need to come from elsewhere.

Did this answer your question?