The wallet envelope
Every wallet request has the same outer shape. payload is the action-specific part and the per-call ids live in payload.meta.
{
"request_id": "0198a1d0-9a30-7f08-a7dd-713e4fd33db0",
"ts": "2026-09-13T12:00:00Z",
"operator_id": "0197aaaa-0000-7000-a000-000000000002",
"action": "debit",
"correlation": {
"provider_slug": "pragmatic",
"provider_round": "round-82721",
"provider_ref": "spin-99182"
},
"payload": {
"meta": {
"request_id": "0198a1d0-9a30-7f08-a7dd-713e4fd33db0",
"op_id": "op-bet-48912",
"operator_id": "0197aaaa-0000-7000-a000-000000000002",
"session_id": "0198a1cb-4f37-7ee8-8e7d-08d39925aec0",
"player_ref": "player-10428",
"launch_token": "lt-abcdef123456",
"provider_slug": "pragmatic",
"game_code": "vs20olympgate",
"round_id": "round-82721",
"provider_ref": "spin-99182",
"ts": "2026-09-13T12:00:00Z"
},
"money": {
"currency": "EUR",
"amount": 100,
"exponent": 2
}
}
}
| Field | Meaning |
|---|---|
op_id | The business operation. The same bet has the same op_id on every retry. Store your answer under it. |
request_id | This one network attempt. Changes on every retry. Logs only. |
round_id | Groups the operations of one game round. Never an idempotency key. |
session_id, launch_token | The player session and the token you gave at launch. Absent on session-less credits, such as promo payouts. |
player_ref | Your player id, passed through unchanged. |
correlation | The game provider’s own ids, for support tickets. |
Ignore fields you don’t know. New optional fields may appear. GA never removes or renames anything published.
Two answers
Success:
{
"status": "ok",
"data": {
"operator_tx_id": "tx-op-982103",
"balance_after": {
"currency": "EUR",
"amount": 14900,
"exponent": 2
},
"debited": {
"currency": "EUR",
"amount": 100,
"exponent": 2
}
}
}
Refusal, always HTTP 200, the reason in the body, no balance:
{
"status": "error",
"error": {
"code": "ERROR_CODE_INSUFFICIENT_FUNDS",
"message": "Player available balance is below requested debit amount",
"retryable": false
}
}
You may add retry_after (a duration such as "5s") to a retryable code. attributes is a free-form string map for your own diagnostics. GA doesn’t read it.
Money
{
"currency": "EUR",
"amount": 100,
"exponent": 2
}
amountis an integer in minor units.100withexponent: 2is 1.00 EUR.exponentis 0 to 18. GA never sends less than 2.- Take the exponent from the message, never from your own currency table. If it differs from what you hold the player’s currency in, refuse with
ERROR_CODE_CURRENCY_MISMATCH. - On your wallet
amountis a JSON number:100. On GA’s own APIs (chapter 4) it’s a JSON string:"100", because proto3 JSON encodes every 64-bit integer as a string. Same value, same minor units. A parser that accepts both is the safe choice.
Data formats
| Value | Format |
|---|---|
| Currency | Uppercase letters and digits, 3 to 10 characters. International Organization for Standardization (ISO) 4217 for fiat (EUR), exchange ticker for crypto (USDT). You and GA agree the exponent per currency at onboarding. |
| Money | {currency, amount, exponent}. See §2.6. Number on your wallet, string on GA’s APIs. |
| Timestamps | RFC 3339 in Coordinated Universal Time (UTC): 2026-09-13T12:00:00Z. aggregates.date is YYYY-MM-DD. |
| Language | Two lowercase letters: en, de. Regional forms (en-GB) fall back to en. |
Ids (player_ref, op_id, round_id, game_id, session_id) | Opaque strings. GA sets no length limit. Keep yours under 128 characters. GA’s session ids are universally unique identifiers (UUIDs). |
wager_weight_bp, fee_bp | Basis points, 0 to 10000. |
Unknown fields in any message must be ignored. Unknown enum values in a known field are refused with INVALID_REQUEST.