Balance history API
Call GET /v1/pay/balance/transactions?limit=20&since_sequence=0 with a Live key carrying wallet:read and the existing HMAC authentication. Send no body. limit is 1–100, default 20. since_sequence is a canonical decimal string from 0 to 9223372036854775807. Duplicate parameters, cursor and unknown parameters are rejected. The additional run_generation parameter below is accepted only with a Test key on the Sandbox hostname.
Each immutable row represents this Merchant's real journal entries. Its stable ID starts with btxn_. bucket_deltas contains:
| Field | Meaning |
|---|---|
available_minor | Signed change to available funds |
pending_clearing_minor | Signed change to pending and held clearing funds combined |
reserved_minor | Signed change to funds reserved for payouts |
Amounts are signed decimal strings in minor units; sequences are decimal strings. Use exact decimal arithmetic or BigInt, rather than converting them to JavaScript Number. This profile has no scalar amount_minor, balance_after, transaction category or unproven source association. Principal, fee and reversal journals remain separate rows.
Continue and reconcile
A successful body contains contract: merchant_balance_history_v1, schema_version: 1, livemode: true, currency: THB, data, has_more, next_sequence, checkpoint and as_of. Each row contains only id, object: balance_transaction, sequence, currency, created_at and bucket_deltas.
Deduplicate by ID and apply each vector once. Pass next_sequence back as since_sequence until has_more is false. Empty pages preserve your supplied sequence. The last page reaches checkpoint.sequence; its three bucket totals have been checked against the authoritative balances in one snapshot. The sequence orders publication, while created_at is the real journal booked timestamp. These orders can differ. Historical backfill uses genuine existing journals without claiming to reconstruct their original commit order.
Incomplete publication or a balance mismatch returns 503 history_not_ready with Retry-After: 1. Wait and reuse your previous sequence; do not invent missing funds. Once history is ready, a cursor ahead of the checkpoint returns 422 invalid_cursor. 429 rate_limited with Retry-After: 60 means the read quota is exhausted. A recovery request can consume two read attempts because authority is checked again after publication.
Sandbox
Sandbox history comes from its genuine ledger. Start with since_sequence=0; the response includes livemode:false, environment:sandbox, run_generation and coverage, in addition to the common fields. Row IDs start with sbxbtxn_l_, pending clearing is always "0", and fees are already included in the real net ledger deltas. A deposit cancellation without a ledger movement creates no history row.
For continuation, send both returned values, for example ?since_sequence=3&run_generation=2. A nonzero sequence requires run_generation (integer 1–2147483647). Sequences start again in each run; keep deduplication and continuation state separately for each generation. A different generation returns 409 sandbox_run_changed with details.current_run_generation. Start from zero in that generation and replace your current-run view. Previous runs remain available through the existing archived Backoffice reports.
coverage contains state: complete|partial, retention_days:30 and min_since_sequence. Sandbox retains 30 days. Start-from-zero reads begin after this floor; they do not invent an opening transaction for an expired prefix. A positive cursor below the floor returns 410 sandbox_history_cursor_expired with details.run_generation and details.min_since_sequence. Restart from zero and treat partial history as a retained slice, rather than summing it from a zero opening balance. If every row has expired, the page is empty and next_sequence equals the floor and checkpoint sequence.
Missing or unreconciled coverage returns 503 sandbox_balance_history_not_ready, retryable:false. An ahead cursor in a ready current run returns 422 invalid_cursor; missing generation with a positive cursor returns 422 validation_error. The existing TEST quota returns 429 sandbox_rate_limited, retryable:true. A temporary service or authentication-storage outage can return 503 service_unavailable, retryable:true. Sandbox responses have no Retry-After header and never use the LIVE publisher or LIVE data.
The existing Sandbox reports and tools remain available within their documented scope.
