---
updatedAt: 2026-09-30T15:54:20.000Z
---

Fetch the complete documentation index at: https://docs.avvio.xyz/llms.txt. Use this file to discover all available pages before exploring further. Append .md to any documentation page URL to get its markdown version.

# Register recipients

A recipient is the person or company you pay, plus the bank details that reach
them in their local currency. SDK methods keep the older name, beneficiary.

1. Read the corridor's fields with `GET /recipients/{orgId}/corridors`.
2. Register the recipient with `POST /recipients/{orgId}`.
3. Store `method.destinationAccountId` and pay it with
   [Send a payout](/payouts/).

## 1. Read the corridor's fields

Read the fields before you render a bank-details form:

```http
GET /recipients/{orgId}/corridors
x-api-key: avvio_live_…
```

```json
{
  "capabilities": { "exactOutput": true, "indicativePricing": true },
  "corridors": [
    {
      "currency": "MXN",
      "fields": [
        { "id": "clabeNumber", "title": "CLABE (18 digits)", "type": "string",
          "required": true, "pattern": "^[0-9]{18}$", "checksum": "clabe" }
      ],
      "limits": { "min": "1.00", "max": "5000.00" }
    }
  ]
}
```

| Property     | What it holds |
| ------------ | ------------- |
| `currency`   | Always present. Key on this |
| `fields`     | The bank details to collect. Always `id`, `title`, `type`, `required`; `pattern`, `checksum`, `description`, `options` when defined |
| `limits`     | Floor and ceiling, when the routing publishes them. They change per routing, so don't hardcode them |
| `country`    | Only when the routing publishes one (the sandbox doesn't). Don't key on it |
| `settlement` | Always `null`; windows, cutoffs and name-matching rules are not published. A payout carries `expectedSettlementAt` when its network reports one |

`checksum` names a check the server applies on top of `pattern`. A value that
fails it is refused with `400 VALIDATION_ERROR`, and `errors[]` names the check.
Run the same check in your form.

| `checksum` | What it checks |
| ---------- | -------------- |
| `clabe`    | Digit 18 of a Mexican CLABE checks digits 1-17: weights 3, 7, 1 repeating, each product mod 10 before summing, then `(10 - sum mod 10) mod 10` (Banxico's algorithm; implementations often drop the inner mod) |

The sandbox, never production, accepts the sample `012345678901234567` despite
its wrong check digit; test your form with a valid CLABE such as
`012180000080004471`. Other fields have no rule beyond `pattern` on our side. A
network rejection is also a `VALIDATION_ERROR`, with the network's reason in
`errors[]` when it gives one.

> [!WARNING]
> **Never hardcode form fields**
> Fields depend on how your organization is routed. MXN on one routing needs
> only `clabeNumber`; another needs bank name, branch code and account number.
> A hardcoded form breaks after a re-route.
> [Recipient details by country](/coverage/recipient-details/) shows each set.

## 2. Register the recipient

```http
POST /recipients/{orgId}
x-api-key: avvio_live_…
Idempotency-Key: 7b2e3f81-91a3-481d-91b4-2b13c7a00f2e
Content-Type: application/json

{
  "type": "individual",
  "name": "María González",
  "email": "maria.gonzalez@example.com",
  "country": "MX",
  "externalId": "emp_42_beneficiary_1",
  "endUserId": "customer_42",
  "method": {
    "kind": "fiat",
    "currency": "MXN",
    "recipientDetails": {
      "clabeNumber": "012180000080004471"
    }
  }
}
```

```json
{
  "id": "cmf3k2xa10004q8b7r5t8u1vw",
  "name": "María González",
  "externalId": "emp_42_beneficiary_1",
  "method": {
    "kind": "fiat",
    "currency": "MXN",
    "last4": "4471",
    "status": "active",
    "destinationAccountId": "sbx_acct_MXN_4471_ae66cbc5"
  }
}
```

| Field | Required | Notes |
| --- | --- | --- |
| `type` | Yes | `individual` or `business` |
| `name` | Yes | Non-empty |
| `method` | Yes | Fiat: `kind: "fiat"`, an ISO 4217 `currency`, and `recipientDetails` keyed by the corridor's field ids. `method.rail` defaults to the corridor's rail |
| `email` | No | A contact address for your records, returned on reads, never used for routing. Stored as `null` when omitted; must be valid when sent |
| `country` | No | ISO 3166 alpha-2 |
| `externalId` | No | 1–128 characters, your id for this recipient and account. Not a substitute for `Idempotency-Key` |
| `endUserId` | No | 1–128 characters, the party sending through your platform (the employer in payroll), never the person paid. Omit it when you are the sender |

Re-sending an `externalId` with the same account returns the existing recipient,
also with `201`, so compare `id`s to detect a repeat. With a different account
it is `409 BENEFICIARY_EXTERNAL_ID_CONFLICT`.

A crypto recipient takes `kind: "crypto"` and an `address`. Create accepts EVM,
Solana and Bitcoin addresses, but only an EVM (`0x…`) address can be paid
today: a payout to any other address fails with `400 VALIDATION_ERROR`. It is
paid in USDC on Base from your organization's wallet, with no fee and
`sourceAmount` equal to `destinationAmount`. Creating the recipient needs no
special scope; paying it needs the `crypto_payouts` scope on the key, or the
payout fails with `400 CRYPTO_PAYOUTS_DISABLED`.

## 3. Store the account id

You pay `method.destinationAccountId`, not the recipient `id`. On a repeat,
`method` is the account the call matched.

## Listing recipients

Pass `endUserId` for one end user's address book, or omit it for the whole
organization. `limit` is 1 to 100 (default 50); pass `nextCursor` back as
`cursor`.

```http
GET /recipients/{orgId}?endUserId=customer_42&limit=50
x-api-key: avvio_live_…
```

The response is `{ scope, recipients, hasMore, nextCursor }`, the one list with
rows under `recipients` instead of `data`. `scope` echoes the filter. Each row's
`id` is the `recipientId`, and `paymentMethods[]` holds each account's
`destinationAccountId`.

## Adding payment methods

```http
POST /recipients/{orgId}/{recipientId}/methods
x-api-key: avvio_live_…
Idempotency-Key: <uuid>
Content-Type: application/json

{
  "kind": "fiat",
  "currency": "EUR",
  "recipientDetails": {
    "iban": "DE89370400440532013000"
  }
}
```

The response is the whole recipient, with `method` set to the added account.
Accounts match on currency and the corridor's account fields (for MXN,
`clabeNumber`), not the holder's name, so sending one again adds nothing. The
same account under another recipient is refused with
`409 BANK_ACCOUNT_ALREADY_LINKED`, carrying `existingRecipientId` and
`existingMethodId`.

## Reading one back

```http
GET /recipients/{orgId}/{recipientId}          # our id
GET /recipients/{orgId}/external/{externalId}  # your id for them
```

Lists carry only `last4`. To show the full account, fetch one method's details:

```http
GET /recipients/{orgId}/{recipientId}/methods/{methodId}/details
```

## Correcting and removing

```http
PATCH /recipients/{orgId}/{recipientId}
```

`PATCH` changes contact details only: name, email, phone, country and `type`.
A validated bank account can't be edited; add the correct one as a new method
and delete the old one:

```http
DELETE /recipients/{orgId}/{recipientId}/methods/{methodId}   # one way of paying them
DELETE /recipients/{orgId}/{recipientId}                      # the recipient and every method on it
```

Deleting cancels nothing in flight. The recipient leaves every list, `GET`
answers `404`, and a payout to its accounts is refused with
`404 DESTINATION_ACCOUNT_NOT_FOUND`. Its `externalId` is released. Deleting a
recipient keeps its row (a soft delete); deleting a single method erases that
method's row. Re-adding the same
account later works; the new recipient takes over the network's account id.
