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

# Integrate FX Swaps

> Step-by-step guide to integrate the FX Swap flow, tested end to end in the sandbox environment.

<Tip>
  **Sandbox base URL:** `https://sandbox-revo-api.raliopay.com`
</Tip>

## Prerequisites

* The **FX feature must be enabled on your account profile**, with pricing configured for each currency pair you want to test. In sandbox this is provisioned by the RalioPay team — contact support if a pair you need returns `This FX quote cannot be processed at the moment. Please contact support.`
* A user with **verified KYC/KYB** and two **active accounts in different currencies** (e.g. an `EUR` wallet and a `USD` wallet).
* The origin account must have balance. You can fund it with a sandbox payin.
* To receive the [FX webhook events](/webhooks-events/fx/overview), register your webhook endpoint and subscribe it to `swap_processing`, `swap_completed`, and `swap_failed`.

## Full test flow

<Steps>
  <Step title="Locate the two accounts">
    Call [List user accounts](/api-reference/user-accounts/list-user-accounts) and note the `accountId` of the origin wallet (`aid` in the URL) and of the destination wallet (`recipient.accountId` in the body).
  </Step>

  <Step title="Create a quote">
    Lock a rate for 60 seconds:

    ```bash theme={null}
    curl -X POST "https://sandbox-revo-api.raliopay.com/api/v1/account/users/{uid}/accounts/{aid}/fx-quotes" \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: application/json" \
      -d '{
        "fromAmount": 100000,
        "recipient": { "accountId": "e582cef6-a320-4d0d-afda-446ba16725ac" }
      }'
    ```

    ```json Response theme={null}
    {
      "quoteId": "q1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
      "origin": { "amount": 100000, "currency": "EUR" },
      "destination": { "amount": 106607, "currency": "USD" },
      "fee": {
        "amount": 1500,
        "currency": "EUR",
        "breakdown": { "systemFee": 1000, "merchantFee": 500 }
      },
      "quote": {
        "rate": "1.0823",
        "pair": "EUR/USD",
        "timestamp": "2026-06-22T11:00:00Z",
        "validUntil": "2026-06-22T11:01:00Z"
      }
    }
    ```
  </Step>

  <Step title="Execute the swap">
    Reference the quote within its 60-second window:

    ```bash theme={null}
    curl -X POST "https://sandbox-revo-api.raliopay.com/api/v1/account/users/{uid}/accounts/{aid}/fx-swaps" \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: application/json" \
      -d '{
        "quoteId": "q1a2b3c4-d5e6-4f7a-8b9c-0d1e2f3a4b5c",
        "ownReference": "FX-SWAP-2026-001",
        "description": "Currency exchange EUR to USD"
      }'
    ```

    The response returns the order in `PENDING` state with a `detailUrl` you can poll. Alternatively, execute **without a quote** at the market rate by omitting `quoteId` and sending `recipient` plus exactly one of `fromAmount` / `toAmount`:

    ```json theme={null}
    {
      "ownReference": "FX-SWAP-2026-002",
      "description": "Currency exchange EUR to USD",
      "fromAmount": 100000,
      "recipient": { "accountId": "e582cef6-a320-4d0d-afda-446ba16725ac" }
    }
    ```
  </Step>

  <Step title="Receive the webhooks">
    Your endpoint receives [`swap_processing`](/webhooks-events/fx/swap-processing) when the swap is delegated to the provider, followed by [`swap_completed`](/webhooks-events/fx/swap-completed) once the destination wallet is credited. In sandbox this happens within seconds.
  </Step>

  <Step title="Verify the result">
    Call [Get FX swap order details](/api-reference/accounts/get-fx-swap-order-details) with the order ID and confirm `state` is `SETTLED` and `settledAt` is set. The destination account balance reflects the credited amount.
  </Step>
</Steps>

## Testing the expired-quote error

Create a quote, wait longer than 60 seconds, then execute with its `quoteId`. The API returns `422` with code `QUOTE_EXPIRED` — no order is created and no funds are held.

## Simulating a failed swap

<Info>
  A `FAILED` swap is triggered by the external provider rejecting the execution. Simulating a failure in sandbox is **coming soon**. In the meantime, make sure your integration handles the [`swap_failed`](/webhooks-events/fx/swap-failed) event: when it fires, the hold on the origin funds has been released and `data.amounts.creditedAmount` is omitted from the payload.
</Info>
