status table and which destination a payout goes to are on Beneficiaries & compliance.
Agents: fetch this page as plain markdown at
https://docs.usecleff.com/api-reference/beneficiaries.md.compliance_status is pending, compliant or suspended, the same for a person (KYC) and a business (KYB); a payout needs compliant.
Acknowledge compliance
When you register a Beneficiary withacknowledged: true, you confirm that your Business has verified who they are. The Beneficiary becomes compliant right away and acknowledged_at is set. If you leave the flag out or send false, the Beneficiary stays pending until you call POST /v1/beneficiaries/{id}/compliance-acknowledgment.
Register a bank account
A Beneficiary’s destinations are bank accounts and debit cards. You can register a bank account; only the Beneficiary can add a card, through a collection link. Only US bank accounts in USD are supported today.
A request without an account number fails with
error.code = "validation_failed" and details[].code = "missing_account_identifier".
Any other country or currency is refused with
BANK_ACCOUNT_VALIDATION_FAILED (details[].code
is country_not_payable or currency_not_payable). This applies when you register or import bank
details.Import Beneficiaries you have already verified
POST /v1/beneficiaries/import creates a Beneficiary, registers their bank account, and records your compliance acknowledgment in one step, so each row is ready to be paid. Use it when you already have verified identity and bank details for many people.
Send between 1 and 500 rows as JSON. File uploads and CSV aren’t accepted.
Every row needs acknowledged: true. A row without it fails with BENEFICIARY_ACKNOWLEDGMENT_REQUIRED. If you don’t have bank details, or haven’t verified the people, use POST /v1/beneficiaries/bulk-invites instead so each person submits their own.
Send the rows
POST /v1/beneficiaries, plus a bank object with the same fields as POST /v1/beneficiaries/{id}/bank-accounts. The required identity fields depend on beneficiary_type.
Read the results
The response is always200, even if some rows failed. results has one entry per row, in the same order, with its index:
Example where the second row failed:
BENEFICIARY_EMAIL_ALREADY_EXISTS and aren’t duplicated.
Check payout_usable as well as status. A created row is always compliant, but payout_usable tells you whether you can pay it yet.
Row errors use the same codes as the rest of the API:
Handle a refused request
These return a normal error response instead of per-row results. Nothing is saved, so it’s safe to retry:
If another request registers one of the same emails at the same moment, the whole import is refused with
409 BENEFICIARY_EMAIL_ALREADY_EXISTS and nothing is saved. Retry, and the conflicting row is reported on its own.
The 1 MiB limit works out to about 2 KB per row at 500 rows, which normal rows stay well under. If you hit it, send fewer rows per request.
Only the
business_admin role can import. A business_ops key gets a 403. Both roles can use
the single-Beneficiary endpoints.beneficiary.bank_account.registered webhooks, because the response already has the full result. Read imported accounts from results. A created row with payout_usable: true can be paid right away.
Call the endpoints
- Create a Beneficiary:
POST /v1/beneficiaries - Import Beneficiaries:
POST /v1/beneficiaries/import(1 to 500 rows) - Update a Beneficiary:
PUT /v1/beneficiaries/{id} - List Beneficiaries:
GET /v1/beneficiaries(paginated:limitdefaults to 25, max 100; pass the previous response’snext_cursorascursorto get the next page) - Get a Beneficiary:
GET /v1/beneficiaries/{id} - Export Beneficiaries:
GET /v1/beneficiaries/export - Archive a Beneficiary:
DELETE /v1/beneficiaries/{id}(can’t be undone; frees the email; payouts already in progress continue) - Acknowledge compliance:
POST /v1/beneficiaries/{id}/compliance-acknowledgment
- Invite Beneficiaries:
POST /v1/beneficiaries/collection-invites - Invite in bulk from CSV rows:
POST /v1/beneficiaries/bulk-invites - Cancel an invite:
DELETE /v1/beneficiaries/{id}/collection-requests/{requestId}
destination_id). The paths are named for bank accounts, but a destination is either kind:
- List destinations:
GET /v1/beneficiaries/{id}/bank-accounts(bank accounts and cards, each withkind; numbers masked to the last 4 digits) - Register a bank account:
POST /v1/beneficiaries/{id}/bank-accounts - Delete a destination:
DELETE /v1/beneficiaries/{id}/bank-accounts/{accountId}(a bank account or a card; refused while a payout awaiting approval is headed to it) - Set the declared destination:
PUT /v1/beneficiaries/{id}/declared-destination(one the Beneficiary already holds and that can be paid)
declared_destination_id on GET /v1/beneficiaries/{id}. The Beneficiary sets it through a collection link and you set it with the request above; the last write wins. A payout that names a destination goes there instead, whether or not it is the declared one.
Scope access to one Business and environment
A Beneficiary belongs to one Business and one environment. Requests for a Beneficiary your key can’t see return404; see Scope a key to one Business and environment.