# Transactions > Bet, win, refund and rollback; transaction states; and why the ledger is append-only. Source: https://casinoapipro.com/docs/transactions Four endpoints move money, and one reads it back. All four are idempotent on your `transaction_id` — read [Idempotency](/docs/idempotency) before writing retry logic. ## Debit a bet `POST /v1/transactions/bet` — auth: operator-api **Body** | Name | Type | Required | Description | | --- | --- | --- | --- | | `transaction_id` | string | yes | Yours. The idempotency key for this movement. | | `player_id` | string | yes | The player being debited. | | `game_id` | string | yes | The game's slug, e.g. `"mines"`. | | `round_id` | string | yes | Yours, not ours. You mint the round id and reuse it for every movement in that round — the bet, the win, and any refund. It is how we group them. | | `amount` | decimal string | yes | The stake, e.g. `"10.00"`. Never a JSON number. | | `currency` | string | yes | ISO 4217. Must match the session. | | `session_id` | string | no | The session this bet belongs to. Optional, but supply it — it is what ties the movement to a sitting in reporting. | | `metadata` | object | no | Flat key/value pairs echoed back on reads. Strings, numbers and booleans only. | ```bash curl -X POST https://api.casinoapipro.com/v1/transactions/bet \\ -H "Authorization: Bearer $ACCESS_TOKEN" \\ -H "Content-Type: application/json" \\ -d '{ "transaction_id": "bet_9f21c4", "player_id": "your_player_42", "game_id": "mines", "round_id": "round_7c31ea", "amount": "10.00", "currency": "GHS", "session_id": "sess_kgv5ws4d" }' ``` ```json { "transaction_id": "bet_9f21c4", "round_id": "round_7c31ea", "status": "COMPLETED", "type": "BET", "amount": "10.00", "currency": "GHS", "balance": "489.50", "created_at": "2026-08-29T15:04:21.118Z" } ``` ## Credit a win `POST /v1/transactions/win` — auth: operator-api The same body shape as a bet. Reuse the `round_id` you sent with the bet — that is what pairs them. A win of zero is legal and closes the round as a loss. ```json { "transaction_id": "win_9f21c4", "player_id": "your_player_42", "game_id": "mines", "round_id": "round_7c31ea", "amount": "24.00", "currency": "GHS" } ``` ## Refund `POST /v1/transactions/refund` — auth: operator-api Returns a specific earlier bet in full, naming it with `reference_transaction_id`. Use it when a round cannot be completed — a game fault, a session closed mid-round, a provider outage. ```json { "transaction_id": "refund_9f21c4", "reference_transaction_id": "bet_9f21c4", "player_id": "your_player_42", "game_id": "mines", "round_id": "round_7c31ea", "amount": "10.00", "currency": "GHS", "reason": "round could not be completed" } ``` ## Rollback `POST /v1/transactions/rollback` — auth: operator-api Voids an earlier movement in full, naming it with `reference_transaction_id`. Where a refund says "give the stake back", a rollback says "this never happened" — it is the correction of record when a round is annulled. > **A rollback takes no amount** > > It is always for the full value of what it reverses. Letting a caller name an amount invites a partial rollback that neither side can reconcile afterwards. ```json { "transaction_id": "rollback_9f21c4", "reference_transaction_id": "bet_9f21c4", "player_id": "your_player_42", "reason": "round annulled" } ``` > **Refund or rollback?** > > Refund the stake when the _player_ should be made whole for a round that could not finish. Roll back when the _movement itself_ was wrong and the books must not show it. If in doubt, refund: it is the one that leaves a clearer audit trail. ## Retrieve a transaction `GET /v1/transactions/{transactionId}` — auth: operator-api Looks up by your id, which means you can always resolve the ambiguity after a timeout: ask us what happened to it. ```bash curl https://api.casinoapipro.com/v1/transactions/bet_9f21c4 \\ -H "Authorization: Bearer $ACCESS_TOKEN" ``` ## Transaction states | Name | Type | Meaning | | --- | --- | --- | | `PENDING` | in flight | Claimed, but your wallet has not confirmed. A retry with the same id resolves it. | | `COMPLETED` | terminal | Your wallet confirmed. The ledger entry is written and immutable. | | `FAILED` | terminal | Your wallet refused. No money moved. | | `ROLLED_BACK` | terminal | Reversed by a later rollback. The original entry remains — the reversal is a new one. | ## The ledger is append-only - Entries are never updated or deleted; a correction is a new entry pointing at the old one. - Enforced by a database trigger, not by convention, so a bug in our code cannot rewrite history. - A report run twice returns the same answer, even after a correction.