Tutorial: Create and manage bank recipients

A bank_recipient is an external bank account that you can pay from one of your bank_accounts. Before sending money to anyone you need a recipient, and (usually) that recipient needs to be enrolled at your bank.

🚧

Recipients are per source account, not per company.

A recipient belongs to one bank account — the one in its owner_id. The same person
enrolled in two of your accounts is two different recipients, with two different ids.

Before transferring, look the recipient up scoped to the account that pays:
GET /v1/banking/bank_recipients?owner_id={sender_id} — not by name alone. A transfer whose
recipient_id belongs to another account is rejected with
422 "Recipient must belong to same account as transaction".

This tutorial covers:

  1. Creating a recipient.
  2. Listing and updating recipients.
  3. Enrolling a recipient at the bank (and the trigger-code flow when the bank requires SMS confirmation).
  4. Common queries.

1. Create

curl -X POST 'https://api.cardda.com/v1/banking/bank_recipients' \
  -H "Authorization: Bearer $API_KEY" \
  -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "owner_id": "BANK_ACCOUNT_ID",
    "name": "Acme Vendor",
    "rut": "765432107",
    "bank_id": "cl_banco_de_chile",
    "account_type": "corriente",
    "account_number": "1234567890",
    "email": "[email protected]",
    "alias": "ACME-MAIN"
  }'

Important fields:

FieldTypeRequiredNotes
owner_iduuidyesThe bank account this recipient is enrolled in. It is what makes the recipient usable as recipient_id for transfers whose sender_id is that same account. Omitting it returns 404.
namestringyesLegal name of the recipient. Length and character set depend on the bank of the owner_id account — the one you enrol into — not on the recipient's bank_id. Caps, strictest first: Itaú 30, BCI / BICE / STP 40, Santander / Scotiabank / Cardda wallet 60, Banco de Chile 64; Banco Security, Banco Internacional and BCP set none. Over the cap you get 422 "Validation failed: Name is too long (maximum is N characters)". Two owner banks also restrict the characters: BICE takes unaccented ASCII letters and spaces only — no digits, no punctuation, no accents (422 "Validation failed: Name recipient's names only can contain letters", so Proveedor ABC SpA is fine and Acme 2 S.A. is not) — and BCI takes A-zÀ-ú0-9 - ñÑ & . @ ( ). Write for the tightest account you enrol from and the same name travels everywhere.
rutstringyes (CL)Tax id, digits and an optional uppercase K only — no dots, no hyphen. 765432107, not 76.543.210-7 nor 76543210-7.
bank_idstringyesBank slug, e.g. cl_banco_de_chile. Get the catalog from GET /v1/banking/banks (no company-id needed; reference data). For a Chilean owner account it must start with cl.
account_typeenumyesChile: corriente, vista, ahorro. Mexico: clabe. Peru: yape.
account_numberstringyesDigits only for Chile, and it cannot start with 0 — strip leading zeros. Length comes from the owner_id account's bank too: BICE 17, BCI 18, 20 elsewhere in Chile; Mexico is an 18-digit CLABE (must be 18 digits, plus a check-digit test) and Peru is exactly 9 digits.
emailstringnoWhere the bank sends payment receipts. Must be a valid address if present, and capped at 40 chars from an Itaú account.
aliasstringdependsRequired at banks without a specific override (BCI, Itaú, Banco Estado, Banco Internacional, Banco Security, …). Optional at Banco de Chile, BICE, Santander, Scotiabank, Cardda wallet, STP and BCP. The cap also comes from the owner_id account's bank, and here too the strictest banks are the ones that require it: Itaú 20, BCI 25, STP 40, BICE 50, Banco de Chile 64, everyone else 60. At BCI the alias takes the same character set as name.
transitionenumnoDefaults to enroll (send it to the bank right away). Pass null to keep it as a local draft — usable in payrolls, not for individual transfers.

Successful response:

{
  "id": "11111111-1111-1111-1111-111111111111",
  "owner_id": "BANK_ACCOUNT_ID",
  "status": "draft",
  "transition": "enroll",
  "name": "Acme Vendor",
  "rut": "765432107",
  "bank_id": "cl_banco_de_chile",
  "account_type": "corriente",
  "account_number": "1234567890",
  "created_at": "2026-04-29T18:00:00Z"
}
📘

There is a single state machine, not two. status moves draft → enrolled → preauthorized → approved (plus deleted), and transition names the operation in flight (enroll, authorize, preauthorize, delete, or null when nothing is running). Only a recipient in approved can receive an individual transfer. There is no enrollment_status field.

2. List, update, delete

# The list you actually want before a transfer: recipients of the paying account
curl -G 'https://api.cardda.com/v1/banking/bank_recipients' \
  --data-urlencode 'owner_id=BANK_ACCOUNT_ID' \
  --data-urlencode 'status=approved' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"

# List with filters — recipients of a specific bank, alphabetically
curl 'https://api.cardda.com/v1/banking/bank_recipients?bank_id=cl_banco_de_chile&_order=asc&_field=name' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"

# Update — regular (non-admin) callers can only change `metadata`
curl -X PATCH 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{ "metadata": { "erp_code": "ACME-001" } }'

# Delete — Cardda wallet recipients only (soft-delete: status becomes `deleted`)
curl -X DELETE 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID"
🚧

PATCH accepts only metadata from regular API callers — alias, email, name and the

account coordinates are not editable through it. To change them, create a new recipient.

DELETE is currently implemented only for Cardda wallet recipients; on a real-bank
recipient the call fails. Deleting a wallet recipient that still has transfers in flight is
rejected with 422 {"code": "recipient/destroy/blocking_transactions"}. A deleted recipient
can no longer receive transfers (422 "Recipient has been deleted").

3. Enroll at the bank

Most banks require you to "enroll" a recipient before sending money. The flow has two shapes — your bank decides which one applies.

The account and the bank key are resolved from the recipient itself (owner_id), so none of the
calls below needs an account id in the body.

3a. Direct enrollment

Most banks accept enrollment with just the bank credentials. Creating the recipient already starts
it (transition: "enroll"); use this endpoint to retry a recipient left in draft (or one that
was deleted):

curl -X POST 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID/enroll' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" -d '{}'

It enqueues the enrollment and answers 201 with transition: "enroll". If the recipient is not
in draft/deleted with no transition in flight, it answers 422 with the unchanged recipient.
Poll GET /v1/banking/bank_recipients/{id} until status reaches enrolled (or approved at
banks that need no second step, such as Banco Security and Banco Internacional).

Banco de Chile additionally requires token and device_id in the body — omitting either
returns 412 Precondition Failed:

curl -X POST 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID/enroll' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{ "token": "03751824", "device_id": "17b05803-ff34-4b17-8850-4d11217a2ae8" }'

3b. SMS / coordinate-card flow

Some banks require a one-time challenge before the recipient reaches approved.

trigger_code is only available where the bank supports it — today BCI, and only while the
recipient is in enrolled. Anywhere else it answers 422 with the unchanged recipient.

# 1. Request the code (bank sends SMS / shows challenge). 204 No Content on success.
curl -X POST 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID/trigger_code' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" -d '{}'

# 2. The user receives the code; you collect it from your UI

# 3. Submit it — the field is `verification_code` (or `token` for token-based banks).
#    You must send at least one of the two, or the call fails with 412.
curl -X POST 'https://api.cardda.com/v1/banking/bank_recipients/RECIPIENT_ID/authorize' \
  -H "Authorization: Bearer $API_KEY" -H "company-id: $COMPANY_ID" \
  -H "Content-Type: application/json" \
  -d '{ "verification_code": "847239" }'

If you receive SMS codes via webhook (PLH issuer), see Verification Codes via Webhook — you can fully automate step 2.

4. Useful queries

# Ready-to-pay recipients of one account — the query to run before a transfer
curl -G 'https://api.cardda.com/v1/banking/bank_recipients' \
  --data-urlencode 'owner_id=BANK_ACCOUNT_ID' --data-urlencode 'status=approved' ...

# All recipients still not enrolled at the bank
curl -G 'https://api.cardda.com/v1/banking/bank_recipients' \
  --data-urlencode 'status=draft' ...

# Search by name — a bare value is an EXACT match, use $regex for partial/case-insensitive
curl -G 'https://api.cardda.com/v1/banking/bank_recipients' \
  --data-urlencode 'name={"$regex":"acme"}' ...

# Recipients of any of two banks
curl -G 'https://api.cardda.com/v1/banking/bank_recipients' \
  --data-urlencode 'bank_id={"$in":["cl_banco_de_chile","cl_banco_bci"]}' ...

See Filters for the full operator reference. There is no $like/$ilike operator —
partial matching is $regex.

Common pitfalls

  • Looking the recipient up without the account scope. The single most expensive mistake: the
    same person can exist as a recipient in several of your accounts. Always filter by
    owner_id={sender_id} before picking a recipient_id.
  • Duplicate detection. Uniqueness is (owner_id, rut, bank_id, account_number) — note it is
    scoped to the owning account, so the same destination in two accounts is legitimately two
    rows. Recreating the same quadruplet in the same account fails with
    422 "Owner already has this recipient".
  • Formatting rut or account_number. Dots and hyphens in rut, or a leading 0 in a
    Chilean account_number, are rejected with a 422. Strip them before sending.
  • Missing alias. At banks that require it, omitting alias fails with
    422 "Alias can't be blank".
  • bank_id mismatch. The recipient bank must be the same as the destination on the transaction. You cannot pay an ACME recipient at Banco de Chile from a Santander source account if the recipient was registered against Itau.

Related


Did this page help you?