> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rolla.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Approve or Reject a Transaction (Sandbox)

> Sandbox only: settles or rejects a transaction that is waiting on a decision, so you do not have to ask the Rolla team to clear your test transactions by hand.

No provider confirms a transfer in sandbox, so simulated deposits and payouts stay `pending` until somebody decides them. `approve` runs the same settlement Rolla's own operators run — the ledger posts, related fee transactions settle with it, the confirmation email is sent and the `transaction.completed` webhook fires. `reject` discards the entries, moves the transaction to `rejected` and fails its related fee transactions, so a failed deposit costs nothing.

Works for every currency. The route does not exist in production, where it returns 404.

Settles or rejects a transaction that is waiting on a decision, so you can run deposit and payout tests end-to-end without asking the Rolla team to clear each one by hand.

In sandbox no provider ever confirms a transfer, so a simulated deposit or payout stays `pending` until somebody decides it. This endpoint is that decision, and it runs **the same settlement path Rolla's own operators run** — the behaviour you test against is the behaviour you get in production.

<Warning>
  **Sandbox / testing only.** This endpoint does **not** exist in production — calls to the production API return `404 Not Found`.
</Warning>

## Request Body

| Field | Type | Required | Description |
| - | - | - | - |
| `action` | string | Yes | `approve` to settle the transaction, `reject` to discard it. Any other value returns `400`. |
| `reason` | string | No | Why it was rejected, up to 500 characters. Recorded on the transaction and included in the rejection email. Ignored for `approve`. |

One endpoint covers both outcomes and **every currency** — USD, NGN, GHS and crypto all settle the same way.

## Approving

```bash theme={null}
curl -X POST "https://api-staging.rolla.xyz/api/v1/external/wallet/transactions/357cf04f-2096-4b45-9c14-e33db131b5dd/decision" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "action": "approve" }'
```

```json theme={null}
{
  "status": 200,
  "message": "Transaction approved successfully",
  "success": true,
  "data": { "transaction": { "id": "357cf04f-2096-4b45-9c14-e33db131b5dd", "status": "completed" } }
}
```

Approving posts the ledger, credits (or debits) the wallet, sends the confirmation email and emits the `transaction.completed` [webhook](/api-reference/webhooks/overview).

## Rejecting

```bash theme={null}
curl -X POST "https://api-staging.rolla.xyz/api/v1/external/wallet/transactions/565aa698-af83-4a9b-9082-a412ecd419b6/decision" \
  -H "X-API-Key: your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "action": "reject", "reason": "simulating a failed inbound wire" }'
```

```json theme={null}
{
  "status": 200,
  "message": "Transaction rejected successfully",
  "success": true,
  "data": { "transaction": { "id": "565aa698-af83-4a9b-9082-a412ecd419b6", "status": "rejected" } }
}
```

Rejecting discards the entries and moves the transaction to `rejected`. The `reason` is sent to the account's notification recipients, exactly as a Rolla-side rejection would be.

## Fees settle with the transaction

A deposit or payout usually carries fee transactions alongside it. They follow the decision automatically — you never decide them separately:

| Decision | The transaction | Its fee transactions |
| - | - | - |
| `approve` | `completed` | `completed` — fees are charged |
| `reject` | `rejected` | `failed`, entries discarded — **nothing is charged** |

So a rejected deposit costs you nothing, and a rejected deposit that would have carried a platform fee leaves no fee behind.

## Which transactions you can decide

Any transaction in **your own book**, plus — if you are a platform partner — any belonging to an account in your tenancy. It is the same set [List Transactions](/api-reference/endpoint/accounts/list-transactions) returns, so anything you can read, you can decide.

A transaction outside that set reports the same `404` as one that does not exist, so an id cannot be used to probe another partner's book.

## When a decision is refused

| Status | Why |
| - | - |
| `400` | `action` missing or not `approve`/`reject` — `"Validation failed"` |
| `400` | Already decided: `"Transaction is not pending or sent: completed"` |
| `400` | A Rolla operator currently holds the transaction, or it is a Mobile Money collection awaiting its provider callback |
| `404` | No such transaction in your book |

Only transactions in `pending`, `sent` or `processing` can be decided. Deciding one twice is refused rather than repeated, so a retry after a timeout is safe.

<Note>
  Simulated deposits are credited by the provider's webhook and can take a few minutes to appear. Poll [Transactions](/api-reference/endpoint/wallet/transactions) until the transaction exists and is `pending`, then decide it.
</Note>

## Who decided it is recorded

Every decision is attributed, the same way a Rolla operator's is. The transaction records the action, the time, the reason, **and the API key that made the call** — so when a business holds several keys you can still tell which integration settled what.

## A full sandbox loop

<Steps>
  <Step title="Simulate the money arriving">
    Call [Simulate an Account Deposit](/api-reference/endpoint/accounts/simulate-deposit) or [Simulate a USD Deposit](/api-reference/endpoint/wallet/simulate-usd-deposit).
  </Step>

  <Step title="Wait for the transaction">
    Poll [Transactions](/api-reference/endpoint/wallet/transactions) until the deposit appears as `pending`.
  </Step>

  <Step title="Decide it">
    `POST /wallet/transactions/{transactionId}/decision` with `approve` or `reject`.
  </Step>

  <Step title="Check what you expected">
    Confirm the balance moved and your webhook endpoint received `transaction.completed`.
  </Step>
</Steps>


## OpenAPI

````yaml POST /wallet/transactions/{transactionId}/decision
openapi: 3.1.0
info:
  title: Rolla Developer API
  description: >-
    API for programmatic transaction management on the Rolla platform. Monetary
    `amount` fields use the smallest unit of the referenced currency unless an
    endpoint says otherwise (e.g. NGN = kobo, USD = cents, where **100 cents =
    USD 1.00**).
  version: 1.0.0
servers:
  - url: https://api.rolla.xyz/api/v1/external
    description: Production server
security:
  - apiKeyAuth: []
paths:
  /wallet/transactions/{transactionId}/decision:
    post:
      summary: Approve or Reject a Transaction (Sandbox)
      description: >-
        Sandbox only: settles or rejects a transaction that is waiting on a
        decision, so you do not have to ask the Rolla team to clear your test
        transactions by hand.


        No provider confirms a transfer in sandbox, so simulated deposits and
        payouts stay `pending` until somebody decides them. `approve` runs the
        same settlement Rolla's own operators run — the ledger posts, related
        fee transactions settle with it, the confirmation email is sent and the
        `transaction.completed` webhook fires. `reject` discards the entries,
        moves the transaction to `rejected` and fails its related fee
        transactions, so a failed deposit costs nothing.


        Works for every currency. The route does not exist in production, where
        it returns 404.
      operationId: decideTransaction
      parameters:
        - name: transactionId
          in: path
          required: true
          description: Rolla's transaction ID (UUID), as returned by List Transactions.
          schema:
            type: string
            format: uuid
          example: 357cf04f-2096-4b45-9c14-e33db131b5dd
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - action
              properties:
                action:
                  type: string
                  enum:
                    - approve
                    - reject
                  description: >-
                    `approve` settles the transaction; `reject` discards it. Any
                    other value is rejected with `400 Validation failed`.
                  example: approve
                reason:
                  type: string
                  minLength: 1
                  maxLength: 500
                  description: >-
                    Why the transaction was rejected. Recorded on the
                    transaction and included in the rejection email sent to the
                    account's notification recipients. Ignored for `approve`.
                  example: simulating a failed inbound wire
            examples:
              approve:
                summary: Settle a pending transaction
                value:
                  action: approve
              reject:
                summary: Reject it, with a reason
                value:
                  action: reject
                  reason: simulating a failed inbound wire
      responses:
        '200':
          description: The transaction after the decision
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    example: 200
                  message:
                    type: string
                    example: Transaction approved successfully
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      transaction:
                        $ref: '#/components/schemas/Transaction'
        '400':
          description: >-
            The body was malformed (`"message": "Validation failed"` with an
            `errors` array), or the transaction cannot be decided — it is not
            `pending`, `sent` or `processing`, it is a Mobile Money collection
            awaiting its provider callback, or a Rolla operator currently holds
            it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 400
                message: 'Transaction is not pending or sent: completed'
        '401':
          description: Invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            No such transaction in your book, or the endpoint was called in
            production (sandbox-only). A transaction belonging to another
            partner reports the same 404 as one that does not exist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                status: 404
                message: 'Transaction not found: 00000000-0000-4000-8000-000000000000'
      servers:
        - url: https://api-staging.rolla.xyz/api/v1/external
components:
  schemas:
    Transaction:
      type: object
      description: >-
        A transaction as the external API returns it. The twelve fields marked
        required are always present (some may be `null`); every other field is
        **omitted** when it does not apply, rather than returned as `null`, so
        branch on a key being present.
      required:
        - id
        - transaction_type
        - status
        - description
        - external_reference
        - fee_amount
        - source_amount
        - destination_amount
        - source_currency
        - destination_currency
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: >-
            Rolla's identifier for the transaction. Stable for its whole life —
            key your records on this.
          example: a1b2c3d4-e5f6-7890-abcd-ef1234567890
        transaction_type:
          type: string
          enum:
            - deposit
            - withdrawal
            - fx_withdrawal
            - swap
            - transfer
            - fee
            - reversal
          description: >-
            What the transaction is. `fee` is a fee charged as a transaction in
            its own right (a parent account paying a sub-account's fee, or a
            white-label margin). `reversal` unwinds an earlier movement.
          example: withdrawal
        status:
          type: string
          enum:
            - draft
            - pending
            - processing
            - completed
            - sent
            - failed
            - rejected
            - refunded
            - negotiating
            - cancelled
            - exception
          description: >-
            Where the transaction has got to. A pending transaction that
            operations have already claimed is reported as `completed`.
          example: completed
        description:
          type: string
          nullable: true
          description: Free-text description recorded against the transaction.
          example: Vendor payment
        external_reference:
          type: string
          nullable: true
          description: >-
            The reference for the transaction — the `externalReference` you sent
            when you created it, or one Rolla generated. Unique only within the
            account that set it.
          example: ref-abc123
        fee_amount:
          type: number
          description: >-
            Every fee attributable to this transaction, combined. Minor units of
            the currency (e.g. `1250` is $12.50), the same scale as the webhook
            payloads. See Amounts & Currencies. A fee booked as its own row
            carries `fee_amount: 0`, so totalling this field down a page counts
            each fee exactly once.
          example: 53
        source_amount:
          type: number
          nullable: true
          description: >-
            Amount debited from the source account. Minor units of the currency
            (e.g. `1250` is $12.50), the same scale as the webhook payloads. See
            Amounts & Currencies.
          example: 500000
        destination_amount:
          type: number
          nullable: true
          description: >-
            Amount credited to the destination. Minor units of the currency
            (e.g. `1250` is $12.50), the same scale as the webhook payloads. See
            Amounts & Currencies.
          example: 499947
        source_currency:
          type: string
          nullable: true
          description: Currency of the source account.
          example: NGN
        destination_currency:
          type: string
          nullable: true
          description: Currency of the destination.
          example: NGN
        exchange_rate:
          type: number
          description: >-
            Swaps only: `destination_amount / source_amount`. Omitted on every
            other transaction type.
          example: 1480
        virtual_account:
          type: object
          description: >-
            Deposits only: the account the money arrived in (or, for a
            mobile-money collection, the wallet it was collected from). Empty
            fields are dropped. Same shape as on the deposit webhook.
          properties:
            id:
              type: string
              format: uuid
            currency:
              type: string
              example: USD
            method:
              type: string
              enum:
                - bank_transfer
                - crypto
                - mobile_money
              example: bank_transfer
            bank_name:
              type: string
              description: Bank transfers only.
              example: Lead Bank
            account_number:
              type: string
              description: Bank transfers only.
              example: '9876543210'
            account_name:
              type: string
              example: Acme Ltd / Rolla
            reference:
              type: string
              description: >-
                Bank transfers only: your own reference for this deposit
                account.
            label:
              type: string
              description: Bank transfers only.
            routing_number:
              type: string
              description: Bank transfers only.
              example: '021000021'
            swift_code:
              type: string
              description: Bank transfers only.
              example: LEADUS33
            wallet_address:
              type: string
              description: Crypto deposits only.
            network:
              type: string
              description: Crypto deposits only.
              example: base
            provider:
              type: string
              description: 'Mobile money only: the operator.'
              example: MTN
            phone_number:
              type: string
              description: 'Mobile money only: the MSISDN funds came from.'
        payer:
          type: object
          description: >-
            Fiat deposits only: who sent the money, as the rail reported it.
            Omitted when the rail reports nothing, and never present on crypto
            deposits or on payouts.
          properties:
            name:
              type: string
              example: Acme Trading Ltd
            account_number:
              type: string
              example: '5551234567'
            bank_name:
              type: string
              example: HSBC
            country:
              type: string
              example: Hong Kong
        payment_rail:
          type: string
          enum:
            - ach
            - wire_domestic
            - wire_international
            - mobile_money
          description: >-
            Which rail an incoming **deposit** arrived on. `ach` is US domestic
            ACH, `wire_domestic` a US domestic wire, and `wire_international` an
            international (SWIFT) wire. Present on USD bank deposits once the
            provider reports the transfer method, and on sandbox simulated
            deposits; **omitted** when the method is not reported, and never
            present on payouts or crypto deposits. Not the same field as the
            webhook payload’s `rail`, which is `fiat` or `stablecoin`.
          example: wire_international
        fee_paid_by:
          type: string
          enum:
            - account
            - parent_account
          description: >-
            Which account the fee came off, in tenancies where a parent account
            pays its sub-accounts’ fees. Omitted on ordinary transactions.
            Reports `account` when the parent could not cover it, because that
            is what happened.
        fee_transaction_id:
          type: string
          format: uuid
          description: >-
            On a movement whose fee was booked as a separate row: the id of that
            fee row. Omitted when the fee was taken from the transaction itself.
          example: c1b2a3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d
        fee_reference:
          type: string
          description: Reference of that fee row, in the form `FEE-XXXXXXXX`.
          example: FEE-8KD02M1X
        related_transaction_id:
          type: string
          format: uuid
          description: >-
            On a `fee` row: the id of the movement that incurred it — the
            reverse of `fee_transaction_id`.
          example: b0a1c2d3-4e5f-4a6b-8c7d-9e0f1a2b3c4d
        related_reference:
          type: string
          description: Reference of that originating movement.
          example: NG-J46RHHKV
        fee_breakdown:
          type: array
          description: >-
            Present only when a white-label partner charged a margin on top of
            Rolla’s fee, so `fee_amount` can be attributed to who earned each
            part.
          items:
            type: object
            properties:
              party:
                type: string
                enum:
                  - platform
                  - partner
                example: platform
              amount:
                type: number
                description: >-
                  Minor units of the currency (e.g. `1250` is $12.50), the same
                  scale as the webhook payloads. See Amounts & Currencies.
                example: 140
              transaction_id:
                type: string
                format: uuid
                nullable: true
                description: >-
                  The row this part was booked as, when it was booked
                  separately.
              reference:
                type: string
                nullable: true
                example: FEE-8KD02M1X
        uetr:
          type: string
          format: uuid
          description: >-
            Cross-border payouts: the UETR (Unique End-to-end Transaction
            Reference) the payment is traced by across every bank that touches
            it. **Omitted** until the correspondent network assigns one — it is
            not returned as `null` — and never assigned on local-rail or
            stablecoin payouts.
          example: 97ed4827-7b6f-4491-a06f-b548d5a7512d
        tracking_codes:
          type: object
          additionalProperties:
            type: string
          description: >-
            Every payment-tracking reference recorded against the payout, keyed
            by name — the `UETR`, plus `IMAD` on corridors that produce one.
            Omitted while there are none.
          example:
            UETR: 97ed4827-7b6f-4491-a06f-b548d5a7512d
            IMAD: 20260818B1QGC01C000123
        metadata:
          type: object
          additionalProperties: true
          description: >-
            The metadata you attached when you created the transaction, back
            exactly as you sent it. Omitted when you sent none.
        beneficiary:
          type: object
          description: >-
            Who a payout was sent to. Returned on payout transactions — by `POST
            /wallet/withdraw` when the payout is created, and by `GET
            /wallet/transaction/{transactionId}` when it is read back.
            **Omitted**, not `null`, when the transaction has no beneficiary;
            the transaction listings never carry it.
          properties:
            account_name:
              type: string
              example: JOHN DOE
            bank_name:
              type: string
              example: Access Bank
            bank_code:
              type: string
              example: '000014'
            account_number:
              type: string
              example: '0123456789'
        created_at:
          type: string
          format: date-time
          description: When the transaction was created.
          example: '2024-01-15T10:30:00.000Z'
        updated_at:
          type: string
          format: date-time
          description: When it last changed.
          example: '2024-01-15T10:30:45.000Z'
    Error:
      type: object
      required:
        - status
        - message
      description: >-
        Error envelope. Some errors add further top-level fields (for example
        `accountBlocked`, `provider`, `missingFields`); the endpoint documents
        them where they apply.
      additionalProperties: true
      properties:
        status:
          type: integer
          description: HTTP status code, repeated in the body as a number
          example: 400
        message:
          type: string
          description: Human-readable description of what went wrong
          example: Validation failed
        code:
          type: string
          description: >-
            Machine-readable error code, present on some errors only. See the
            Errors concept page for the taxonomy.
          example: ACCOUNT_RESTRICTED
        errors:
          type: array
          description: 'Present on `400` validation failures: one entry per offending field'
          items:
            type: object
            properties:
              path:
                type: array
                items:
                  type: string
                example:
                  - amount
              message:
                type: string
                example: >-
                  amount must be a whole number of smallest currency units (e.g.
                  cents)
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Your Rolla API key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.