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

# Pay-outs

> Send crypto from an account wallet and have a bank beneficiary receive fiat.

A pay-out moves value the opposite way from a [pay-in](/guides/finance/virtual-bank-accounts): crypto leaves an account wallet, and a beneficiary receives fiat in their bank account.

Getting there takes three objects, created once and reused for every subsequent pay-out:

```mermaid theme={null}
flowchart LR
    A["Payout bank account<br/><i>whose bank account</i>"] --> C["Payout route<br/><i>bank account + crypto asset</i>"]
    B["Deposit asset<br/><i>which crypto, which chain</i>"] --> C
    C --> D["Payout<br/><i>how much, when</i>"]
```

| Object                  | Scope   | Answers                                          |
| ----------------------- | ------- | ------------------------------------------------ |
| **Payout bank account** | Party   | Whose bank account, on which rail                |
| **Payout route**        | Account | Which bank account pairs with which crypto asset |
| **Payout**              | Route   | How much, and when                               |

The route is where the durable configuration lives. It fixes the beneficiary, the rail, the crypto asset and the fiat currency — so an actual pay-out request carries almost nothing: a route, an amount, and an idempotency key.

## Setup

<Warning>
  Before any of this, the recipient party's **partner-terms consent** must be complete. A verified party
  whose end customer has not yet accepted our partners' terms cannot receive a pay-out — present the
  party's consent link to them first. See
  [Partner-terms consent](/guides/finance/onboarding/partner-terms-consent).
</Warning>

<Steps>
  <Step title="Give the recipient party a PAYOUT_RECIPIENT role">
    Before anything else, the party who owns the destination bank account needs an `ACTIVE`
    [`PAYOUT_RECIPIENT` role](/api-reference/Finance-API/accounts/add-a-party-to-an-account) on the
    paying account, with cleared KYC/KYB.

    A party can hold `PAYOUT_RECIPIENT` **and** `ACCOUNT_HOLDER` on the same account — that's the
    self-payout case, where a user withdraws to their own bank account.

    <Note>This is the "recipient gate". It's checked when you create a route **and again** when you request each pay-out, so revoking the role stops future pay-outs immediately.</Note>
  </Step>

  <Step title="Register the beneficiary bank account">
    [`POST /parties/{partyId}/payout-bank-accounts`](/api-reference/Finance-API/payout-bank-accounts/register-a-payout-bank-account)

    ```json theme={null}
    {
      "rail": "US_ACH",
      "fiatCurrency": "USD",
      "label": "Jane's checking account",
      "accountHolderName": "Jane Doe",
      "railDetails": {
        "accountNumber": "123456789012",
        "abaRoutingNumber": "021000021",
        "accountType": "CHECKING"
      },
      "bankName": "Example National Bank",
      "bankAddress": {
        "street1": "270 Park Avenue",
        "city": "New York",
        "region": "NY",
        "postalCode": "10017",
        "country": "US"
      }
    }
    ```

    This is the **only** endpoint that accepts raw bank credentials. The account number is
    KMS-encrypted at rest immediately and never returned — every read path exposes only
    `accountNumberLast4`. The ABA routing number is a public bank identifier, so it comes back in full.

    <Warning>`bankAddress` is the **beneficiary bank's** address, not the recipient's home address. All five components are required and `country` must be ISO 3166-1 alpha-2, or you get `400 invalid-bank-address`.</Warning>

    The account starts `PENDING` and becomes `ACTIVE` once activated for pay-outs.
  </Step>

  <Step title="Create a route">
    [`POST /accounts/{accountId}/payout-routes`](/api-reference/Finance-API/payout-routes/create-a-payout-route)

    ```json theme={null}
    {
      "payoutBankAccountId": "a1b2c3d4-e5f6-4708-9a1b-2c3d4e5f6071",
      "depositAsset": { "chain": "BASE", "name": "USDC" }
    }
    ```

    You never name a provider — Venly resolves one internally by intersecting provider capability with
    your tenant's enablement, and provider identity is never exposed in any response.

    A route is created `PENDING` and moves `REGISTERING → ACTIVE` as registration completes, or
    `REJECTED` on terminal failure.

    It can also stop at **`AWAITING_OWNERSHIP_PROOF`**, which is the one status that needs something
    from you — see [Proving you control the source wallet](#proving-you-control-the-source-wallet).

    <Warning>**Only `ACTIVE` routes are usable.** Poll [List payout routes](/api-reference/Finance-API/payout-routes/list-payout-routes) until the route reports `ACTIVE` — requesting a pay-out earlier returns `422 route-not-active`.</Warning>
  </Step>
</Steps>

## Proving you control the source wallet

A route can pause at `AWAITING_OWNERSHIP_PROOF`. That happens when the destination requires a Travel-Rule proof that you control the wallet the funds leave from, **and** that wallet is self-custody. Venly holds no key for it, so only its owner can sign.

Registration doesn't fail — it waits. Nothing else moves until you complete this step.

<Steps>
  <Step title="Ask for the message">
    [`POST /accounts/{accountId}/payout-routes/{routeId}/ownership-proof/prepare`](/api-reference/Finance-API/payout-routes/prepare-a-route-ownership-proof) — no body needed.

    Nothing is stored and there's no expiry token, so this is safe to repeat.
  </Step>

  <Step title="Have the owner sign it verbatim">
    <Warning>
      Sign `message` **exactly as returned**. Don't reformat, re-encode, pretty-print or reconstruct it — the signature is checked against those exact bytes, and wrapping extra text around it is rejected even when the signature itself is valid.

      Only an externally-owned account can sign: the scheme is EIP-191, and **ERC-1271 contract signatures are not supported**. A smart-contract wallet cannot complete this step.
    </Warning>
  </Step>

  <Step title="Submit it">
    [`POST .../ownership-proof/complete`](/api-reference/Finance-API/payout-routes/submit-a-route-ownership-proof) with `message` and `signature`. The route leaves `AWAITING_OWNERSHIP_PROOF` and registration resumes.
  </Step>
</Steps>

<Note>
  A `200` from *complete* means the proof was accepted, not that the route is ready. It continues to `ACTIVE` asynchronously — poll the route or [register a webhook](/guides/finance/webhooks). Only an `ACTIVE` route has a `depositAddress`.
</Note>

The message is date-bound, so sign and submit in the same session rather than preparing it one day and submitting the next.

| Code                             | Meaning                                                                        |
| -------------------------------- | ------------------------------------------------------------------------------ |
| `ownership-proof-not-applicable` | This route needs no owner-signed proof, or its source wallet is Venly-managed. |
| `invalid-proof-message`          | The message isn't the canonical text for this route.                           |
| `signature-mismatch`             | The signature doesn't recover the route's wallet address.                      |

## Requesting a pay-out (PULL)

With an `ACTIVE` route, a pay-out is a small request:

```bash theme={null}
curl -X POST https://api.venlyfinance.com/v1/accounts/{accountId}/payouts \
  -H "Authorization: Bearer {access_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "payoutRouteId": "b2c3d4e5-f607-4819-a2b3-c4d5e6f70819",
    "cryptoAmount": 100.00,
    "idempotencyKey": "b7c1e084-3f2a-4d19-9c60-5ae8127b04f3"
  }'
```

Venly sends crypto from the account wallet to the provider's deposit address using the account's
up-front [permit](/guides/finance/permits-and-allowances), and the provider pays fiat to the
beneficiary. This is **PULL** funding — `fundingMode: PULL`.

### A `201` is not success

This is the single most important thing to get right in a pay-out integration.

<Warning>
  Permit allowance and account-wallet balance are deliberately **not** pre-checked. If either is
  insufficient, the request still returns `201` with `status: REQUESTED`, and the asynchronous send then
  fails terminally — the pay-out lands in `FAILED` with a `failureReason`.
</Warning>

So don't treat the `201` as "money sent". Track the real outcome by
[registering a webhook](/guides/finance/webhooks) or polling
[Get a payout](/api-reference/Finance-API/payouts/get-a-payout).

## The pay-out lifecycle

```mermaid theme={null}
stateDiagram-v2
    [*] --> REQUESTED: PULL via API
    REQUESTED --> SENDING
    SENDING --> PROVIDER_PROCESSING
    [*] --> PROVIDER_PROCESSING: PUSH from observed deposit
    PROVIDER_PROCESSING --> COMPLETED
    COMPLETED --> RETURNED: fiat leg bounced
    REQUESTED --> REJECTED
    SENDING --> FAILED
```

| Status                | Meaning                                                                          |
| --------------------- | -------------------------------------------------------------------------------- |
| `REQUESTED`           | Created via the API; awaiting dispatch of the send leg. PULL only.               |
| `SENDING`             | The managed on-chain send is in flight. PULL only.                               |
| `PROVIDER_PROCESSING` | Funds reached the provider's deposit address; awaiting the fiat pay-out.         |
| `COMPLETED`           | The provider reported the fiat pay-out as completed; `settledFiatAmount` is set. |
| `REJECTED`            | Validation failed before dispatch. PULL only. Terminal.                          |
| `FAILED`              | The managed send leg failed terminally. PULL only. Terminal.                     |
| `RETURNED`            | The provider reported the fiat leg as bounced or returned. Terminal.             |

<Note>
  `COMPLETED` is not the end of the story. A fiat leg can still bounce afterwards — a closed account, a
  name mismatch at the receiving bank — moving the pay-out to `RETURNED`. If you release goods or credit
  a ledger on `COMPLETED`, handle `RETURNED` as a reversal.
</Note>

`settledFiatAmount` is the fiat the provider actually paid, and is null until `COMPLETED`. Expect it
to be lower than the crypto you sent, net of fees — reconcile against this field rather than assuming
a 1:1 conversion.

<a id="push-pay-outs" />

## PUSH pay-outs

Not every pay-out starts with an API call. On a **self-custody** account, a customer can send crypto
to the route's `depositAddress` themselves. Venly observes that deposit and creates the pay-out for
you, with `fundingMode: PUSH`.

|            | PULL                                          | PUSH                            |
| ---------- | --------------------------------------------- | ------------------------------- |
| Created by | Your `POST /payouts` call                     | Venly, from an observed deposit |
| Funded by  | Venly, from the account wallet via the permit | The customer, directly          |
| Custody    | Any                                           | Self-custody only               |
| Starts at  | `REQUESTED`                                   | `PROVIDER_PROCESSING`           |

`depositAddress` is exposed **only** for an `ACTIVE` route on a `SELF_CUSTODY` account — read it from
[List payout routes](/api-reference/Finance-API/payout-routes/list-payout-routes) and show it to your
customer.

Both kinds appear in the same read endpoints, so
[List payouts](/api-reference/Finance-API/payouts/list-payouts) gives you the complete picture.
Filter on `fundingMode` if you need to treat them differently.

## Idempotency

`idempotencyKey` is required and must be unique **per company across all idempotent endpoints** — not
just across pay-outs. The full replay behaviour is in
[Idempotency](/guides/finance/idempotency#what-a-replay-actually-returns).

<Warning>
  The one that catches people here: a pay-out that **failed** has spent its key. Retrying with the same key
  returns `422`, not a fresh attempt — generate a new key. Because pay-outs fail asynchronously, this often
  surfaces well after the original `201`.
</Warning>

## Common errors

| Status | Code                       | Cause                                                                    |
| ------ | -------------------------- | ------------------------------------------------------------------------ |
| `400`  | `account-not-active`       | The paying account is not `ACTIVE`.                                      |
| `422`  | `route-not-active`         | The route hasn't reached `ACTIVE` yet, or was `REJECTED`.                |
| `422`  | `route-unresolved`         | The route doesn't belong to this account.                                |
| `422`  | `recipient-not-authorized` | No `PAYOUT_RECIPIENT` role, or the party's KYC/KYB isn't cleared.        |
| `422`  | `recipient-role-inactive`  | The role exists but is `INACTIVE`.                                       |
| `422`  | `unsupported-asset`        | The route's lane is no longer enabled for your tenant.                   |
| `409`  | `route-already-exists`     | A route already exists for this account, bank account and deposit asset. |
| `422`  | `unsupported-combination`  | No single provider resolves for that bank account and deposit asset.     |

## Next steps

<CardGroup cols={2}>
  <Card title="Webhooks" icon="bell" href="/guides/finance/webhooks">
    Get pay-out status changes pushed to you instead of polling.
  </Card>

  <Card title="Permits & allowances" icon="key" href="/guides/finance/permits-and-allowances">
    The permit that makes PULL pay-outs possible on self-custody wallets.
  </Card>

  <Card title="Virtual bank accounts" icon="building-columns" href="/guides/finance/virtual-bank-accounts">
    The pay-in direction: fiat in, crypto out.
  </Card>

  <Card title="Idempotency" icon="rotate" href="/guides/finance/idempotency">
    Safe retries across every write endpoint.
  </Card>
</CardGroup>
