Reporting & BI 9
GET
/api/reports/gamePerformance
Per-game performance report
Params fromDate, toDate, category?, provider?, page/limit
The gap todayToday per-game numbers only exist by downloading EVERY wager row via /api/wagers/search (no paging, ~100MB per week) and folding client-side.
What it generatesOne row per game for the window: handle (total staked), GGR, wager count, unique players, average bet, theoretical RTP as configured, actual RTP ((handle − GGR) ÷ handle × 100), and hold %. This is the whole casino-manager daily view in one call — which games earn, which run hot (actual RTP above theoretical), which are dead inventory.
GET
/api/reports/revenueByDay
Daily revenue series
Params fromDate, toDate, vertical=casino|sports|all
The gap todayThe only aggregate today is /api/reports/transactionSummary, which is one lump for a window — no per-day series, no casino/sports split.
What it generatesAn array of days, each with handle, GGR, NGR (GGR − bonus cost), deposits, withdrawals, net cash, actives, split by vertical. Feeds every line chart in a dashboard without walking raw wagers.
POST
/api/wagers/searchPaged
Paged wager search
Params body: filters + cursor + pageSize (server-capped)
The gap today/api/wagers/search returns the entire result unpaged — a week of history is ~100MB and a month is un-fetchable. This is the single worst operational gap in the atlas.
What it generatesThe same wager rows with a cursor: { rows: [...], nextCursor }. Makes every downstream report incremental and cheap instead of day-walking.
GET
/api/reports/topPlayers
Top winning / losing players
Params fromDate, toDate, direction=winning|losing, vertical?, limit=10
The gap todayRanking players today means folding every wager row for the window per player, client-side.
What it generatesRanked players with net win/loss from the house's perspective, handle, wager count, verticals played, VIP tier and agent. The daily 'who is beating us / who is feeding us' sheet every book runs on.
GET
/api/reports/hourlyActivity
Hourly activity heatmap
Params fromDate, toDate, metric=bets|logins|deposits
The gap todayNo time-of-day dimension exists anywhere in the atlas.
What it generatesA 7×24 matrix of activity counts — staffing the cage/support desk, timing campaign sends, spotting bot-like off-hour patterns.
GET
/api/reports/cohortRetention
Signup-cohort retention
Params months=12
The gap todayNo retention analytics exist upstream; churn is invisible until revenue drops.
What it generatesA matrix: each signup month × months-since-signup → % of the cohort still active and their GGR. The canonical LTV/retention view (standard in GiG Core / EveryMatrix).
GET
/api/reports/bonusCost
Bonus cost & liability report
Params fromDate, toDate, bonusId?
The gap todayBonus operations exist per-player, but no aggregate cost view — the marketing budget is unknowable.
What it generatesPer bonus program: amount granted, converted to cash, forfeited, outstanding (current liability), completion rate, and NGR impact — whether each promotion pays for itself.
GET
/api/reports/scheduled
Scheduled report subscriptions
Params CRUD subroutes: POST /api/reports/scheduled, DELETE …/{id}
The gap todayReport templates exist (13 ops) but nothing delivers them on a schedule.
What it generatesSubscriptions binding a report template + period (daily/weekly/monthly) + recipients; the platform emails the rendered report. The 'Monday 8am P&L in the owner's inbox' feature.
GET
/api/wagers/search?include=fundSource
Fund source on every wager row (cash vs bonus vs free-spin)
Params existing search params + include=fundSource
The gap todayWager rows carry no wallet_type/fund_source, so every hold and RTP figure the warehouse can compute is all-wallet (cash and bonus mixed) — the casino reporting plan (docs/casino-reports-plan.md §2) calls this the single highest-priority platform ask: without it, real cash hold, NGR by wallet, bonus ROI and abuse detection are all unbuildable.
What it generatesEach wager row gains fundSource: 'cash' | 'bonus' | 'free_spin' (and the split stake when one bet draws on both wallets). Finance can then publish cash hold without the bonus contamination caveat, and the bonus ledger can compute true wagering-requirement progress per instance.
Player administration 13
GET
/api/customers/{customerId}/activitySummary
One-call player 360
Params days=30
The gap todayA player 360 today needs 11 separate calls (minimal, balances, credits, bonuses ×3, logins, transactions, wagers…).
What it generatesProfile + balances + limits + KYC state + last login/IP + N-day totals (handle, net, wager count, deposits, withdrawals) + recent ledger in one response. Cuts the support screen from 11 round-trips to 1.
GET
/api/customers/{customerId}/gameHistory
Player per-game breakdown
Params fromDate, toDate
The gap todayCannot answer 'what does this player play and how does it treat them' without the full wager dump.
What it generatesPer game/league for one player: handle, net, wager count, actual RTP experienced. Drives VIP-host conversations and RG interventions ('this player is chasing losses on one high-volatility slot').
PUT
/api/customers/{customerId}/limits
Set responsible-gambling limits
Params body: dailyDeposit, weeklyLoss, monthlyLoss, wagerMax, sessionMinutes, realityCheckMins (null clears)
The gap todayThe atlas has NO limit operations at all — an MGA/UKGC-licensed operator legally cannot run without them.
What it generatesThe stored limit set + effective-from timestamps (limit increases must be delayed 24h–7d per most regulations; decreases apply instantly). GET twin returns current limits + pending changes.
POST
/api/customers/{customerId}/exclusion
Self-exclusion / cool-off
Params body: kind=SELF|OPERATOR|COOL_OFF, months|days, registry?
The gap todayExclusion is a licensing requirement (MGA unified registry, GamStop-style) with zero atlas support.
What it generatesThe exclusion record: kind, term, start, registry it was propagated to; while active the platform must refuse logins/deposits and suppress ALL marketing. GET twin lists history.
GET
/api/customers/exclusions
The self-exclusion register, readable
Params query: updatedSince?, page?, pageSize? — ids and states only, no personal data needed
The gap todayToday this endpoint exists and answers 403 to our key, so we can see only the exclusions recorded in our own back office and not the ones players set on the platform itself. That is the register a player uses. Being unable to read it means we cannot tell "not excluded" from "excluded somewhere we cannot see", and a responsible-gambling guardrail that fails closed must therefore treat every player as unverified.
What it generatesOne row per active exclusion: customerId, kind (SELF|OPERATOR|COOL_OFF), start, end or indefinite, and the registry it was propagated to. A history twin, or an updatedSince cursor, would let it be polled cheaply rather than re-read whole.
GET
/api/customers/{customerId}/riskScore
Composite risk score
Params —
The gap todayFraud/AML/RG risk is spread over raw data nobody joins.
What it generates0–100 composite plus per-factor breakdown: payment risk (velocity, instruments), fraud signals (device/IP sharing), RG behavior score (loss-chasing, night play, limit hits), AML flags. The triage number every queue sorts by.
GET
/api/customers/{customerId}/devices
Device & session fingerprints
Params —
The gap todayLogin rows carry IPs only; multi-accounting detection is impossible.
What it generatesDevice fingerprints (hashed), user agents, IPs with geo, first/last seen, and WHICH OTHER customerIds share any of them — the multi-account/bonus-abuse graph in one call.
GET
/api/customers/duplicates
Duplicate account scan
Params match=email|phone|device|ip|payment, threshold
The gap todayDupe detection today is a client-side join over the full customer dump.
What it generatesClusters of accounts sharing identity signals, each with a match confidence and combined bonus cost — the daily bonus-abuse review queue.
POST
/api/customers/{customerId}/freeze
Soft-freeze account
Params body: reason, until?
The gap todayOnly heavyweight status changes exist; investigations need a reversible hold that isn't an exclusion.
What it generatesA freeze record blocking wagers/withdrawals but preserving login + support contact, with audit trail (who froze, why, when it lifts).
GET
/api/customers/segments
Server-side segments
Params CRUD + GET /{id}/members
The gap todayEvery segmentation today is a saved client-side filter that goes stale instantly.
What it generatesNamed segment definitions (rules over deposits, activity, vertical, VIP, dormancy) materialized server-side; /members streams current membership. Feeds campaigns, bulk actions, and stake-factor policies.
POST
/api/customers/{customerId}/notes
Server-side account notes
Params body: text, category, pinned; GET twin lists
The gap todayNotes live only in each back office's local SQLite — support and VIP hosts on different tools can't see each other's history.
What it generatesA shared, audited note stream per player (author, timestamp, category: support/finance/risk/VIP), readable from every console that talks to the platform.
POST
/api/customers/{customerId}/passwordReset
Reset a player password
Params body: mode=TEMP_PASSWORD|EMAIL_LINK|SMS_LINK, notify?
The gap todayThe atlas has password POLICIES (GET/PUT /api/auth/customers/passwordPolicies) and password VALIDATION, but no operation for support to reset an individual player password — the single most common support request a desk gets. Today the only live workaround is POST /api/loginLink.
What it generatesTEMP_PASSWORD mode: a one-time strong temporary password (returned once, must-change-on-login flag set, all sessions invalidated). LINK modes: a signed, expiring reset link delivered by the chosen channel with delivery status. Every reset lands in the audit log with actor + reason.
POST
/api/customers/{customerId}/forceLogout
Kill player sessions
Params —
The gap todayAccount-takeover response requires instantly ending every session; the atlas has admin logout only, nothing per-customer.
What it generatesCount of sessions/tokens invalidated (web, wrapper sessions, kiosk). Pairs with passwordReset in the compromise runbook.
Sportsbook 7
GET
/api/sports/events
Events catalog
Params fromDate, toDate, league?, status=open|live|settled
The gap todayEvents only exist as strings buried in wager-row properties — there is no first-class event entity.
What it generatesEvent objects: id, league, sport, matchup, start time, status, market count. The backbone every sports report joins against.
GET
/api/sports/events/{eventId}/handle
Total amount bet per game
Params byMarket=1, bySide=1
The gap todayThe user's explicit ask — 'total amount bet per game for sports' — currently requires folding the full wager dump.
What it generatesFor one event: total handle, bet count, average bet, and the split by market (ML/spread/total/props/parlay legs) and by side — where the money is, which side the book needs.
GET
/api/sports/liability
Open liability board
Params league?, sport?
The gap todayThe single most important trading number — worst-case payout on open bets — does not exist upstream.
What it generatesPer open event/market: open handle, worst-case and best-case P&L if each outcome hits, current max-bet, bet delay. Sorted by worst case; this is the trader's morning screen (SoftSwiss/GiG ship exactly this).
GET
/api/sports/openBets
Unsettled tickets
Params eventId?, customerId?, minRisk?
The gap todayNo way to list pending tickets without downloading everything and filtering by status.
What it generatesAll unsettled wagers with risk/toWin, event, market, side, placed-at — feeds liability, early cash-out decisions, and void queues.
POST
/api/wagers/{wagerId}/void
Void / regrade a wager
Params body: action=VOID|REGRADE, newResult?, reason
The gap todayTrader operations (bad line, palps, late scratch) have no atlas support — today it's a vendor support ticket.
What it generatesThe corrected wager + a compensating ledger transaction, with an immutable audit entry (who, why, before/after).
PUT
/api/customers/{customerId}/stakeFactor
Player stake-factor profiling
Params body: factor (0.05–5.0), liveDelaySec, note
The gap todaySharp-player management — the core of bookmaking — has zero atlas support.
What it generatesThe player's limit multiplier applied to every market max (0.1 = sharp cut to 10%, 2.0 = trusted recreational doubled), plus live-bet delay. GET twin lists all non-default profiles — the 'sharps board'.
GET
/api/sports/leagues
League reference
Params —
The gap todayLeague/sport taxonomy is embedded in strings; no canonical reference data.
What it generatesLeagues with sport, season dates, default margin target, and per-league handle/GGR rollups for the trailing 30 days.
Payments & finance 6
GET
/api/payments/pending
Unified pending-payments queue
Params family=DEPOSIT|WITHDRAWAL, method?, riskMin?
The gap todayThe withdrawal-approval queue today is /api/transactions/search + client-side filtering, with no risk context.
What it generatesPending items enriched with the player's risk score, KYC state, bonus-abuse flags, lifetime deposits vs withdrawals, and method fees — everything an approver needs on one row.
GET
/api/payments/routes
PSP cascade configuration
Params PUT twin to reorder
The gap todayPayment orchestration (MoneyMatrix-style cascading) is invisible — success rates and failover order live only in the PSP's own dashboards.
What it generatesPer method/PSP: current success rate, average settle time, status, and the cascade order (where a declined transaction retries next). PUT reorders the cascade.
GET
/api/finance/chargebacks
Chargeback case management
Params status?; POST …/{caseId}/evidence
The gap todayChargebacks — the #1 card-payment loss vector — have no atlas presence.
What it generatesCases with reason codes, amounts, deadlines and representment status; POST attaches evidence. Feeds a win-rate report per method/PSP.
GET
/api/finance/reconciliation
PSP settlement reconciliation
Params date, provider
The gap todayNothing verifies that PSP settlement files match the platform ledger.
What it generatesPer provider/day: platform-ledger total vs PSP-reported settlement, fees, and the itemized diff. The daily 'are we actually being paid' check.
POST
/api/transactions/{transactionId}/refund
Refund a transaction
Params body: amount?, reason
The gap todayPartial/full refunds are a support-ticket path today.
What it generatesThe compensating transaction with linkage to the original, audit-stamped.
GET
/api/customers/{customerId}/holds
Balance holds (withdrawal lifecycle)
Params —
The gap todayThe proper cashier flow is: withdrawal request → amount moves to HOLD on the balance (visible, not spendable) → back-office approves (hold settles, money leaves) or declines (hold releases). The real atlas has the decision op (PUT /api/transactions/{id}/pendingStatus) but balances expose no hold state — support cannot tell a player why their spendable balance is lower than their total.
What it generatesEvery active hold on the wallet: pending withdrawal requests (id, amount, method, requested-at), bonus-rollover locks, and dispute freezes — plus the derived availableTotal / heldTotal / withdrawable numbers the player-facing wallet should show.
Compliance (MGA-grade) 5
GET
/api/compliance/amlAlerts
AML alert queue
Params status?, severity?; POST …/{alertId}/status
The gap todayAML monitoring (velocity, structuring, minimal-play flow-through) is a licensing requirement with zero atlas support.
What it generatesRule-generated alerts: customer, rule, severity, amount, SoW/SoF request state, case status. POST moves a case through OPEN → INVESTIGATING → CLEARED/SAR_FILED with an immutable trail.
GET
/api/compliance/sarExport
SAR/STR export
Params alertIds[]
The gap todayFiling a suspicious-activity report means hand-assembling data from a dozen screens.
What it generatesA regulator-format bundle per case: identity, account history, flagged transactions, prior alerts — ready to attach to the FIU filing.
GET
/api/compliance/screening/{customerId}
PEP & sanctions screening
Params refresh=1 to re-run
The gap todayPEP/sanctions checks (OFAC, EU, UN lists) are mandatory at onboarding and periodically; the atlas has nothing.
What it generatesMatch results with list, score, and disposition workflow (confirmed false positive / true match → freeze + report).
GET
/api/compliance/regulatoryReturn
Regulatory return export
Params period=YYYY-MM, jurisdiction
The gap todayMGA (and every EU regulator) requires periodic returns — player counts, GGR by vertical, RG statistics, player-funds balances. Assembling them is days of manual work.
What it generatesThe complete return dataset for the period: GGR/NGR by vertical and jurisdiction, tax due, new/active/excluded player counts, limit-setting statistics, and the player-funds segregation balance (liabilities vs segregated account).
GET
/api/audit/log
Back-office audit log
Params actor?, fromDate, toDate, operation?
The gap todayadminUser operations exist but nothing records WHO did WHAT to WHOM — an auditor's first request.
What it generatesEvery privileged action: actor, operation, target entity, before/after values, IP, timestamp. Immutable, exportable.
Bonus engine & CRM 9
POST
/api/bonus/freeSpins
Free-spin grants
Params body: customerIds[]|segmentId, gameId, spins, spinValue, expiresAt
The gap todayThe Bonus tag (17 ops) is cash-bonus only — free spins, the #1 casino retention tool, are absent.
What it generatesGrant records per player with redemption tracking (spins used, winnings converted to bonus balance, expiry). GET twin reports redemption + cost per campaign.
GET
/api/bonus/abuseFlags
Bonus-abuse detection
Params —
The gap todayMulti-account bonus farming silently drains every promotion; detection today is manual cross-referencing.
What it generatesPlayers flagged by rule: duplicate-cluster membership, bonus-to-deposit ratio outliers, min-rollover-then-withdraw patterns, per-flag evidence. Feeds the duplicates queue and grant blocklists.
POST
/api/messaging/campaigns
Segmented campaign send
Params body: segmentId, channel=email|sms|push|onsite, template, schedule
The gap todayMailer ops exist (6) but only raw sends — no segments, no scheduling, no stats.
What it generatesA campaign object; GET …/{id}/stats returns delivered/opened/clicked/converted (deposits within attribution window) and per-player suppressions (RG/comm-prefs). Closes the marketing loop.
GET
/api/gamification/tournaments
Tournaments & leaderboards
Params CRUD + GET /{id}/leaderboard
The gap todayGamification (tournaments, missions, leaderboards — Soft2Bet/Smartico's whole business) has no atlas presence.
What it generatesTournament definitions (vertical, scoring rule, prize pool, dates) and live leaderboards. POST creates; the platform scores automatically from wager flow.
GET
/api/gamification/missions
Missions / achievements
Params CRUD
The gap todaySame gap as tournaments.
What it generatesMission definitions (rule, reward, active window) + completion counts + per-player progress. Drives the 'quests' retention surface.
GET
/api/customers/{customerId}/churnScore
Churn prediction
Params —
The gap todayDormancy is only visible after it happens.
What it generatesA 0–1 churn probability with drivers (deposit frequency decay, session gap growth, net-loss streak) and a recommended intervention (reload offer / VIP call / rest nudge). Batch twin: /api/reports/churnRisk lists the top-risk cohort.
POST
/api/bonus/coupons
Coupon-code bonus templates (RTG-style)
Params body: code, kind=DEPOSIT_MATCH|FREE_CHIP|CASHBACK, matchPct, maxBonus, sticky, rollover (e.g. 40 for 40x), rolloverBase=B|D+B, maxWin (fixed | multiple-of-deposit | none), allowedGames, expiryDays; GET lists, PUT updates/deactivates
The gap todayRTG-branded casinos run their entire promotion economy on coupon codes redeemed at the cashier — sticky (non-cashable) bonuses, 30x–80x playthrough, max-cashout caps, game restrictions. The atlas Bonus tag (17 ops) can assign amounts to players but has no template/coupon concept, no sticky flag, no max-win cap, and no game weighting.
What it generatesThe stored coupon template with every knob above. Redemptions reference the code; the platform enforces sticky-at-withdrawal removal, rollover accrual with per-category game weights, and the max-win cap at cashout time.
GET
/api/customers/{customerId}/bonusWallet
Player bonus wallet (rollover next to balance)
Params —
The gap todayThe player must SEE their remaining rollover next to their balance — and support must see the same number. The atlas has GET /api/bonus/{id}/rollover per bonus, but nothing aggregated, nothing with sticky/max-win context, and nothing shaped for the wallet display.
What it generatesThe exact wallet strip the player-facing site renders: cash balance, bonus balance with sticky flag (sticky bonus is removed at withdrawal, not cashed), TOTAL remaining rollover in dollars (e.g. "wager $3,120 more to unlock withdrawals"), per-grant breakdown (code, granted, rollover done/required, expiry), and any active max-win caps. One call, one strip.
GET
/api/bonus/instances
Bonus instance ledger with instance ids stamped on wagers and transactions
Params customerId?, status?, fromDate/toDate
The gap todayAssigned bonuses come back as an undated blob per customer with no stable instance id, and neither wager rows nor BONUS transactions reference which grant they belong to — so wagering-funnel completion, per-campaign cost and abuse recycling (plan §1-E) can only be approximated. Second-priority platform ask after fundSource.
What it generatesOne row per bonus instance (stable bonusInstanceId, template/coupon code, granted/converted/forfeited/expired amounts, rollover done vs required, timestamps), with the same bonusInstanceId stamped on every wager row and BONUS-family transaction it touches. Turns the bonus ledger from grant-side accounting into a true instance roll-forward.