Skip to main content
Look up the fields, formats and endpoints for Beneficiaries and their destinations. For developers already integrating; what a Beneficiary is, the 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.
A Beneficiary (the payee) is a person or business your Business pays. Its 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 with acknowledged: 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

Each row takes the same identity fields as 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 always 200, even if some rows failed. results has one entry per row, in the same order, with its index: Example where the second row failed:
A failed row saves nothing, so there’s nothing to clean up. Resend only the failed rows. If you resend the whole request, rows that were already created come back as 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.
Imported rows don’t send 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: limit defaults to 25, max 100; pass the previous response’s next_cursor as cursor to 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
Invites, where the Beneficiary fills in their own details on a Cleff-hosted form:
  • 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}
Destinations, which a payout pays to (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 with kind; 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)
The one a payout goes to when it names none is 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 return 404; see Scope a key to one Business and environment.