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

# Sandbox environment

> Build and test against the sandbox, including deterministic off-ramp outcomes, before you move to production.

Every integration starts in the sandbox. When you register an application in the
business portal, it is issued sandbox
[application credentials](/authentication/api-keys) by default. The sandbox
exposes the same API contract as production, so you can build and test the full
flow before requesting production access.

<Info>
  **Base URL:** `https://api.zeam.money/gw/v1`

  The sandbox uses the **same base URL as production**. Which environment a
  request runs in is determined by your application's registration and role, not
  by the host. Sandbox and production credentials are separate.
</Info>

## What the sandbox is for

* Authenticating: issuing an access token and calling protected endpoints.
* Building the payment flow end to end: wallets, connectors, quotes, and P2P,
  swap, and off-ramp payments.
* Exercising deterministic [off-ramp outcomes](#off-ramp-testing).
* Testing your [webhook](/webhooks/overview) handling.
* Validating your error handling against the
  [RFC 7807 contract](/platform/errors).

## Sandbox versus production

* **Same base URL.** Sandbox and production share `https://api.zeam.money/gw/v1`; your
  application's registration determines the environment, so there is no host to
  switch.
* **Separate credentials.** Sandbox and production credentials are distinct and
  not interchangeable. See [Register your application](/guides/register-your-application).
* **No real value moves.** Off-ramp payouts are simulated (see below); no funds
  reach a real mobile-money or bank account.
* **Same contract.** Endpoints, request and response shapes, and error formats
  match production, so your integration code does not change between them.

<Warning>
  Treat the sandbox as a shared, non-production environment. Do not send real
  personal data, real customer identifiers, or anything you could not put in a
  test system.
</Warning>

## Off-ramp testing

In the sandbox, off-ramp execution is simulated so you can test both outcomes
deterministically. The simulator decides the result from the **sender's MSISDN**
(mobile number):

* Sender MSISDN ends in **`111`**: the off-ramp **succeeds**.
* Sender MSISDN ends in **`000`**: the off-ramp **fails**.
* Any other ending is treated as a **failure**.

<Warning>
  These rules apply to the **sandbox only**. Never rely on them in production,
  where the real payout provider determines the outcome.
</Warning>

<Info>
  The simulator evaluates the sender MSISDN that the platform resolves for the
  off-ramp. The gateway's `POST /v1/payments/offramp` request does not itself
  carry a sender MSISDN field.
</Info>

Off-ramps are asynchronous: a submission returns `202 Accepted` with a
`transactionRecordId`, and the simulated outcome arrives afterwards. Poll the
transaction or receive [webhooks](/webhooks/events) to observe it.

<Steps>
  <Step title="Submit the off-ramp">
    ```bash cURL theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    curl -sS -X POST https://api.zeam.money/gw/v1/payments/offramp \
      -H "Authorization: Bearer $ZEAM_TOKEN" \
      -H "x-zeam-auth: $ZEAM_APP_SECRET" \
      -H "x-association-id: $ZEAM_ASSOCIATION_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "idempotencyKey": "3d594650-3436-11e5-bf7f-0002a5d5c51b",
        "transactionId": "sbx-offramp-001",
        "walletId": "66666666-6666-6666-6666-666666666666",
        "beneficiaryId": "77777777-7777-7777-7777-777777777777",
        "paymentDestinationId": "aaaa1111-2222-3333-4444-555566667777",
        "quoteId": "bbbb1111-2222-3333-4444-555566667777",
        "sendingAsset": "USDC:GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN",
        "purpose": "FAMILY_SUPPORT",
        "sourceOfFunds": "SALARY"
      }'
    ```

    ```json 202 Accepted theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    {
      "transactionRecordId": "d94b0f8a-6d1e-4a2c-9f3b-2a7c1e5d8b90",
      "intentId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "accepted",
      "isIdempotent": false
    }
    ```
  </Step>

  <Step title="Confirm a success (sender MSISDN ends in 111)">
    A successful simulation moves the transaction to `completed` and emits the
    [`IntentSuccessful`](/webhooks/events) webhook event.

    ```json GET /v1/transactions/{transactionRecordId} theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    {
      "transactionRecordId": "d94b0f8a-6d1e-4a2c-9f3b-2a7c1e5d8b90",
      "type": "offramp",
      "status": "completed",
      "sendAmount": 100.00,
      "sendCurrency": "USD",
      "receiveAmount": 1750.00,
      "receiveCurrency": "ZAR",
      "completedAt": "2026-01-15T10:32:07Z"
    }
    ```
  </Step>

  <Step title="Confirm a failure (sender MSISDN ends in 000)">
    A failed simulation moves the transaction to `failed` and emits the
    [`IntentFailed`](/webhooks/events) webhook event.

    ```json GET /v1/transactions/{transactionRecordId} theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    {
      "transactionRecordId": "d94b0f8a-6d1e-4a2c-9f3b-2a7c1e5d8b90",
      "type": "offramp",
      "status": "failed",
      "failureReason": "off-ramp payout failed",
      "errorCode": "RAMP_FAILED"
    }
    ```
  </Step>
</Steps>

## Limitations

The sandbox is for integration testing, not load testing or production traffic.

## Moving to production

When your integration works end to end in the sandbox, review
[Moving to production](/access/production-promotion).
