155API

Acceptance Testing

Self-certify your wallet integration before go-live


Acceptance testing lets you certify your wallet integration on your own, before go-live. It replays the real signed wallet callbacks (/balance, /bet, /win, /rollback) against your staging wallet and returns a pass/fail report — so you can confirm everything works end-to-end without waiting on a coordinated test.

Runs on staging only (acceptance.stagingmarbles.io). Each money-moving check places a real, small bet/win/rollback on your staging wallet for the test player you specify, so make sure that player is funded.

Run it yourself

You drive the run with two calls:

  1. POST /acceptance/run — kicks off the suite and returns the full report in the response body when it finishes (a run takes up to ~90s).
  2. GET /acceptance/run/{runId} — re-fetches a stored run by its runId (handy to re-read or share a result; you can only fetch your own runs).
POST https://acceptance.stagingmarbles.io/acceptance/run
GET  https://acceptance.stagingmarbles.io/acceptance/run/{runId}

Authentication

Authentication is by source IP — call from one of the IP addresses you whitelisted with us when your integration was set up. There is no token or signature to manage; we resolve which integration you are from the calling IP, so you do not send an integrationId (any integrationId in the body is ignored for operator calls). A request from a non-whitelisted IP is rejected with 401.

Request

Field names are PascalCase under testPlayer (they map directly to the harness' TestPlayer fields):

POST /acceptance/run HTTP/1.1
Host: acceptance.stagingmarbles.io
Content-Type: application/json

{
  "operatorId": "your-operator-id",
  "partnerId": "your-partner-id",
  "testPlayer": {
    "ClientPlayerID": "a funded standing test player",
    "ClientSessionID": "that player's last real session id",
    "Currency": "USD",
    "GameID": "a gameId whitelisted for you (GameAccessRules)"
  }
}

Omit checks to run all applicable checks. To run a subset, add a checks array of check ids (e.g. "checks": ["balance_read"]); ["all"] is the same as omitting it.

Request fields

FieldTypeDescription
operatorIdstringYour operator identifier (the operatorId you send in /game/url). Used for the game-whitelist check and the wallet-URL fallback.
partnerIdstringYour brand / partner identifier (the partnerId you send in /game/url). Needed for the game_whitelisted launch check.
testPlayer.ClientPlayerIDstringA funded standing test player on your staging wallet.
testPlayer.ClientSessionIDstringA current/recent session id for that player (some wallets validate it).
testPlayer.CurrencystringISO 4217 currency code (e.g. USD).
testPlayer.GameIDstringA gameId whitelisted for your integration (its GameAccessRules).
checksarrayOptional. Subset of check ids, or ["all"]. Defaults to all applicable checks.

Where do these values come from?

These are the same identifiers you already send us when you launch a game via POST /game/url — there's nothing new to generate:

  • ClientPlayerID — your own unique id for the player. Use a real, funded test player on your staging wallet (the money-moving checks place small real bets, so it needs a balance). Tip: launch a game once as that player and copy the clientPlayerId / clientSessionId from your own /game/url request — those are exactly the values to use.
  • ClientSessionID — your session id for that player. Use a recent/live one your wallet recognises (the same value you'd pass in /game/url).
  • GameID — any game whitelisted for you (the game_whitelisted check below confirms it is). You can list your games with POST /game/games.
  • operatorId / partnerId — the same values you send in every /game/url call.

Try it with curl

Start a run from a whitelisted integration IP:

curl -sS -X POST https://acceptance.stagingmarbles.io/acceptance/run \
  -H "Content-Type: application/json" \
  -d '{
    "operatorId": "your-operator-id",
    "partnerId": "your-partner-id",
    "testPlayer": {
      "ClientPlayerID": "your-funded-test-player",
      "ClientSessionID": "that-players-session-id",
      "Currency": "USD",
      "GameID": "a-whitelisted-game-id"
    }
  }'

The POST returns the full report. To re-fetch it later by its runId:

curl -sS https://acceptance.stagingmarbles.io/acceptance/run/run_327b8db7-0da9-448c-80cb-b78d09a5ce91

For a first, no-risk run, pass only the read-only checks: "checks": ["signature_accepted", "signature_rejected", "unknown_player", "balance_read"]. These never move money — they confirm reachability, signature handling, and that we can read the test player's balance.

The checks

The suite runs in four phases. Phase 1 is reachability/auth, Phase 2 confirms the game is launchable, Phase 3 exercises the full wallet lifecycle, and Phase 4 verifies amount precision. Advisory checks warn rather than fail — a not-met advisory never blocks go-live.

Phase 1 — Reachability & auth (read-only)

CheckWhat it proves
signature_acceptedConfirms we can reach your wallet and your endpoint accepts our signed /balance request. Fail = your wallet is unreachable, the endpoint is wrong, or it rejected our request (signature, IP allowlist, or TLS).
signature_rejected (advisory)A request signed with the wrong key should be refused. Advisory — if you secure the wallet by allowlisting our egress IPs instead of verifying the X-Marbles-Signature, this warns rather than fails.
unknown_player (advisory)A /balance for a non-existent player should return an error, not a balance. Advisory — returning a balance for an unknown player is sloppy but not a blocker.

Phase 2 — Launch (read-only)

CheckWhat it proves
game_whitelistedThe gameId you launch must be in your operator's allowed games (GameAccessRules). Fail = this game isn't whitelisted for you — players would get a 'game not found' on launch. Ask us to add it to your allowed games.

This check needs both operatorId and partnerId; it is skipped if either is missing.

Phase 3 — Wallet lifecycle

CheckWhat it proves
balance_readWe can read the test player's balance via /balance. Fail = your /balance didn't return a usable balance for the configured test player/session.
settle_netzeroA bet followed by an equal win returns the balance to where it started. Fail = your wallet didn't reconcile bet+win to the same balance.
bet_debitA /bet debits exactly the stake. Fail = a different amount was debited — most often the wallet rounds/truncates amounts (we use 5-digit precision) or applies a fee.
insufficient_fundsA bet larger than the balance must be rejected with INSUFFICIENT_BALANCE and no debit. Fail = you accepted an over-balance bet or debited anyway.
win_creditA /win credits exactly the payout. Fail = the credited amount didn't match the payout — usually amount rounding/precision.
lossSettling a bet as a loss (/win with amount 0) leaves only the stake debited. Fail = the balance moved by an unexpected amount on a zero-amount settle.
rollback_zeroA /rollback of a bet returns the balance to exactly where it started. Fail (common: re-debit) = your rollback moved money the wrong way instead of refunding the stake.
free_betA free bet (isFree:true, amount 0) is accepted with zero debit and settles cleanly. Fail = your wallet rejected the isFree payload or errored settling it. Free bets are enabled by default for new operators.
idempotencyA replayed /win or /bet with the same transactionId must be deduplicated (no double credit/debit, and a duplicate /bet is rejected). Fail = you re-processed a duplicate — add transactionId dedup, or a network retry will double-charge players.

Phase 4 — Precision

CheckWhat it proves
precisionOdd amounts (e.g. 1.23457) reconcile to the exact cent at 5-digit precision. Fail = your wallet rounds or truncates amounts (e.g. to 2 decimals), so balances drift from ours.

The report

The POST (and the GET) return the full report — HTTP 200 even when the run failed; the verdict is in the top-level status. Each check carries its own status, a human-readable reason, the same operator-facing description from the table above, and evidence (balances before/after, HTTP status, etc) — so a failed check tells you exactly what to fix.

{
  "runId": "run_327b8db7-0da9-448c-80cb-b78d09a5ce91",
  "integrationId": "your-integration",
  "environment": "staging",
  "status": "passed_with_warnings",
  "checks": [
    {
      "id": "signature_accepted",
      "phase": 1,
      "title": "Our signature is accepted",
      "status": "pass",
      "reason": "correctly-signed /balance accepted",
      "evidence": { "httpStatus": 200 }
    },
    {
      "id": "signature_rejected",
      "phase": 1,
      "title": "A bad signature is rejected",
      "status": "warn",
      "reason": "operator ACCEPTED a request signed with a wrong key (HTTP 200) — it is not verifying signatures"
    },
    {
      "id": "balance_read",
      "phase": 3,
      "title": "Balance reads for the test player",
      "status": "pass",
      "reason": "balance read",
      "evidence": { "balance": 3564378000 }
    },
    {
      "id": "rollback_zero",
      "phase": 3,
      "title": "Rollback nets to zero",
      "status": "pass",
      "reason": "rollback returns to start (catches re-debit operators)",
      "evidence": { "before": 3564378000, "after": 3564378000, "expectedDelta": 0, "actualDelta": 0 }
    }
  ]
}

Run status

statusMeaning
runningThe run is still in progress — transient; you only see this if you poll GET /acceptance/run/{runId} before it finishes.
passedEvery applicable check passed — you're good to go live.
passed_with_warningsAll hard checks passed; an advisory check was flagged (not a blocker).
failedAt least one hard check failed — see the checks array.
blockedA prerequisite was missing (e.g. no funded test player).

Per-check status

statusMeaning
passThe check passed.
failThe check failed — read reason and description for the fix.
warnAn advisory check was not met (e.g. you allowlist our IPs instead of verifying our signature). Not a go-live blocker.
blockedA prerequisite was missing for this check (e.g. no funded test player).

Caveats

Staging only, and the test player must be funded

  • The harness runs on staging only (acceptance.stagingmarbles.io). Operator self-cert against the production endpoint is refused with 403 — production is internally gated.
  • Use a real, funded standing test player. The money-moving Phase-3/4 checks place small real bets/wins/rollbacks on that player's staging wallet; with no balance they fail (or block). The read-only checks never move money.
  • Advisory checks (signature_rejected, unknown_player) warn rather than fail — a not-met advisory yields passed_with_warnings, not failed.

Error statuses

HTTPReason
400Missing testPlayer.GameID, or invalid JSON body.
401Calling IP is not whitelisted for any integration.
403Operator self-cert was attempted against the production endpoint (staging only).
404The requested runId doesn't exist (or isn't yours).

For the wallet-callback error statuses your endpoint returns (e.g. INSUFFICIENT_BALANCE), see Error Codes.

On this page