# Envelope, money and data formats

## 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`.

```json
{
  "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:

```json
{
  "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:

```json
{
  "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

```json
{
  "currency": "EUR",
  "amount": 100,
  "exponent": 2
}
```

- `amount` is an integer in minor units. `100` with `exponent: 2` is 1.00 EUR. `exponent` is 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** `amount` is 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`.
