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

# Payout Flow Guide

> Execute an end-to-end fiat payout — from recipient onboarding to bank settlement

This guide walks through a complete off-ramp payout: onboarding a recipient, setting up their virtual account, getting a quote, and executing a payment that settles to their bank.

## Prerequisites

* A Relayer API key (see [Authentication](/get-started/authentication))
* Your workspace activated with the Payout module enabled
* KYB completed for your workspace (handled through the Relayer dashboard — one-time setup)

<CodeGroup>
  ```bash Environment theme={null}
  export RELAYER_API_KEY="rk_client_key_v1_your_key_here"
  export RELAYER_BASE_URL="https://testnet.relayer.fi/v1"  # sandbox; use https://api.relayer.fi/v1 for production
  ```
</CodeGroup>

<Note>
  Business onboarding (KYB) is completed through the dashboard before you can use the Payout API. Once approved, every endpoint below becomes available.
</Note>

## Steps

<Steps>
  <Step title="Create a recipient (beneficiary)">
    Register the person or business you want to pay. This creates a recipient record in Relayer — no fiat-rails call yet.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/recipients \
        -H "Authorization: ApiKey $RELAYER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "Maria Garcia",
          "email": "maria@example.com",
          "ownerType": "individual",
          "address": {
            "street": "Paseo de la Reforma 123",
            "city": "Mexico City",
            "country": "MX",
            "postalCode": "06600"
          }
        }'
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/recipients`, {
        method: "POST",
        headers: {
          "Authorization": `ApiKey ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          name: "Maria Garcia",
          email: "maria@example.com",
          ownerType: "individual",
          address: {
            street: "Paseo de la Reforma 123",
            city: "Mexico City",
            country: "MX",
            postalCode: "06600",
          },
        }),
      });
      const { data } = await response.json();
      const beneficiaryId = data.id;
      ```
    </CodeGroup>
  </Step>

  <Step title="Invite the recipient to add their bank account">
    Generate an invite link. The recipient visits this URL to submit their bank details securely. The token is valid for 7 days.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/recipients/$BENEFICIARY_ID/invite \
        -H "Authorization: ApiKey $RELAYER_API_KEY"
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(
        `${RELAYER_BASE_URL}/v1/payout/recipients/${beneficiaryId}/invite`,
        {
          method: "POST",
          headers: { "Authorization": `ApiKey ${apiKey}` },
        }
      );
      const { data } = await response.json();
      // data.inviteUrl — send this to the recipient
      console.log(`Send invite to recipient: ${data.inviteUrl}`);
      ```
    </CodeGroup>

    Once the recipient submits their bank details, the account is registered with the regulated fiat rails partner. You can list bank accounts at any time via `GET /v1/payout/recipients/{id}/accounts`.

    <Tip>
      If you already have the recipient's bank details on file, you can skip the invite and call `POST /v1/payout/recipients/{id}/accounts` directly.
    </Tip>
  </Step>

  <Step title="Create a withdrawal address (Step 1 of account setup)">
    Link the recipient's bank account to a crypto chain. Stablecoins sent to this address trigger the off-ramp transfer. This call is idempotent.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/setup/liquidation-address \
        -H "Authorization: ApiKey $RELAYER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "beneficiaryId": "ben_abc123",
          "beneficiaryAccountId": "acc_xyz789",
          "chain": "polygon",
          "currency": "usdc"
        }'
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(
        `${RELAYER_BASE_URL}/v1/payout/accounts/setup/liquidation-address`,
        {
          method: "POST",
          headers: {
            "Authorization": `ApiKey ${apiKey}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            beneficiaryId: "ben_abc123",
            beneficiaryAccountId: "acc_xyz789",
            chain: "polygon",
            currency: "usdc",
          }),
        }
      );
      const { data } = await response.json();
      const withdrawalAddress = data.address;
      ```
    </CodeGroup>
  </Step>

  <Step title="Create a virtual account / CLABE (Step 2 of account setup)">
    Create an MXN SPEI virtual account tied to the withdrawal address. Returns the CLABE your client uses for fiat deposits. This call is idempotent.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/setup/virtual-account \
        -H "Authorization: ApiKey $RELAYER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "beneficiaryId": "ben_abc123",
          "beneficiaryAccountId": "acc_xyz789"
        }'
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(
        `${RELAYER_BASE_URL}/v1/payout/accounts/setup/virtual-account`,
        {
          method: "POST",
          headers: {
            "Authorization": `ApiKey ${apiKey}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            beneficiaryId: "ben_abc123",
            beneficiaryAccountId: "acc_xyz789",
          }),
        }
      );
      const { data } = await response.json();
      console.log(`Client CLABE: ${data.clabe}`);
      ```
    </CodeGroup>

    Share the CLABE with your client. When they deposit MXN to this CLABE, the funds settle through the withdrawal address to the recipient's bank.
  </Step>

  <Step title="Get a payment quote">
    Before executing a settlement, get a quote to confirm the rate and fees.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/quote \
        -H "Authorization: ApiKey $RELAYER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "beneficiaryId": "ben_abc123",
          "amount": 1000,
          "currency": "MXN"
        }'
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/accounts/quote`, {
        method: "POST",
        headers: {
          "Authorization": `ApiKey ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          beneficiaryId: "ben_abc123",
          amount: 1000,
          currency: "MXN",
        }),
      });
      const { data } = await response.json();
      // data.rate — exchange rate applied
      // data.fees — settlement fees
      // data.netAmount — recipient receives this amount
      const quoteId = data.quoteId;
      ```
    </CodeGroup>
  </Step>

  <Step title="Execute the payment">
    Trigger the settlement. This initiates the off-ramp transfer to the recipient's bank.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/execute \
        -H "Authorization: ApiKey $RELAYER_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "quoteId": "qte_ghi789",
          "beneficiaryId": "ben_abc123"
        }'
      ```

      ```typescript Node.js theme={null}
      const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/accounts/execute`, {
        method: "POST",
        headers: {
          "Authorization": `ApiKey ${apiKey}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          quoteId: "qte_ghi789",
          beneficiaryId: "ben_abc123",
        }),
      });
      const { data } = await response.json();
      // data.reference — payment reference for tracking
      // data.status — "pending" initially
      console.log(`Payment initiated: ${data.reference}`);
      ```
    </CodeGroup>

    <Warning>
      Payment execution is irreversible once initiated. Confirm the quote details and recipient bank account before calling this endpoint.
    </Warning>

    Settlement typically completes within 1-2 business days via SPEI.
  </Step>

  <Step title="Track the order">
    Every payment is represented as an order. Track status via the unified orders endpoint:

    ```bash cURL theme={null}
    curl $RELAYER_BASE_URL/v1/orders/$ORDER_ID \
      -H "Authorization: ApiKey $RELAYER_API_KEY"
    ```

    To list recent orders for this recipient:

    ```bash cURL theme={null}
    curl $RELAYER_BASE_URL/v1/payout/recipients/$BENEFICIARY_ID/orders \
      -H "Authorization: ApiKey $RELAYER_API_KEY"
    ```

    You can also check payment status by reference:

    ```bash cURL theme={null}
    curl $RELAYER_BASE_URL/v1/payout/accounts/$REFERENCE/status \
      -H "Authorization: ApiKey $RELAYER_API_KEY"
    ```
  </Step>
</Steps>

## On-ramp variant (fiat → stablecoin)

If instead of off-ramp you want to receive stablecoins from a fiat deposit, use the on-ramp endpoints:

```
POST /v1/payout/onramp/quote                — Get fiat → stablecoin rate
POST /v1/payout/onramp/deposit-accounts     — Create or get a deposit account (idempotent)
GET  /v1/payout/onramp/deposit-accounts/{id}/events  — Track lifecycle events
```

The deposit account is a permanent fiat account (e.g. a CLABE in Mexico) tied to a stablecoin wallet. When the client deposits fiat, the stablecoin lands at the wallet.

## Cancelling an order

An order in `awaiting` status can be cancelled before settlement:

```
POST /v1/orders/{id}/cancel
```

The call is idempotent — calling it twice on the same awaiting order is safe. Once the order has moved past `awaiting`, cancellation is no longer possible.

## Next Steps

<CardGroup cols={2}>
  <Card title="Endpoints" icon="code" href="/payout/endpoints">
    Full endpoint reference for accounts, on/off-ramp, recipients, and orders.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Interactive schemas and try-it playground.
  </Card>

  <Card title="Glossary" icon="book-open" href="/shared/glossary">
    Settlement, on-ramp, off-ramp, and other domain terms.
  </Card>

  <Card title="Error Reference" icon="triangle-exclamation" href="/shared/error-reference">
    API error codes and handling patterns.
  </Card>
</CardGroup>
