API referenceBeneficiaries

Beneficiaries

Saved recipients you pay regularly. Idempotent upsert + per-currency rail validation. Soft-deleted beneficiaries are preserved for audit but excluded from active lookups.

The beneficiary object

{
  "object": "beneficiary",
  "id": "ckben_xxxxxxxxxxxx",
  "name": "JANE DOE",
  "email": "recipient@example.com",
  "phone": "+2348012345678",
  "currency": "NGN",
  "env": "live",
  "bank_code": "044",
  "bank_name": "Access Bank",
  "account_number": "0690000032",
  "account_name": "JANE DOE",
  "interac_email": null,
  "interac_first_name": null,
  "interac_last_name": null,
  "verification": "verified",
  "is_archived": false,
  "is_blacklisted": false,
  "source": "manual",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-05-05T12:34:56Z"
}

Field notes:

  • account_name is the bank-of-record name returned by NUBAN (or the merchant-supplied name for non-resolvable currencies). name is what the merchant labels them as (often the same).
  • verification is one of pending / verified / failed — set when NUBAN resolves successfully.
  • source is manual when created via API/dashboard, auto_saved when created automatically from a successful payout.
  • interac_* fields populate only on CAD beneficiaries; bank-rail fields are null in that case.
  • FX beneficiaries (GBP / USD / EUR) additionally carry nested bank + address blocks and an optional type (individual / business) — see the FX shape below.

FX beneficiary object (GBP / USD / EUR)

GBP / USD / EUR rows return the same top-level fields plus nested bank + address blocks and an external_reference:

{
  "object": "beneficiary",
  "id": "ckben_xxxxxxxxxxxx",
  "name": "Jane Doe",
  "currency": "USD",
  "env": "live",
  "type": "individual",
  "account_name": "Jane Doe",
  "external_reference": "your-ref-123",
  "bank": {
    "bank_name": "Example Bank",
    "method": "ach",
    "account_type": "checking",
    "routing_number": "021000021",
    "sort_code": null,
    "iban": null,
    "bic_code": null,
    "swift_code": null
  },
  "address": {
    "street": "1 Main St",
    "city": "New York",
    "state": "NY",
    "zip_code": "10001"
  },
  "verification": "verified",
  "created_at": "2026-01-15T10:00:00Z",
  "updated_at": "2026-05-05T12:34:56Z"
}

The bank block only populates the identifiers relevant to the currency (sort_code for GBP, routing_number for USD, iban + bic_code for EUR); the rest are null.

POSTUpsert a beneficiary

POST/v1/beneficiaries

Idempotent on the tuple (merchant, currency, env, bank_code, account_number). If a beneficiary already exists for those identifiers:

  • The existing row is updated with new name / email / phone (you can correct typos).
  • Soft-deleted rows are restored.
  • Blacklisted rows refuse update — un-blacklist first.
Body parameters — NGN (flat bank-rail)
currencystringRequired

NGN.

namestringRequired

Your label for the recipient. Preserved as your label — the bank-of-record name is captured into account_name automatically.

account_numberstringRequired

NUBAN account number.

bank_codestringRequired

Bank code (CBN or NIP form).

bank_namestringRequired

Bank display name.

emailstring

Optional metadata.

phonestring

Optional metadata.

Body parameters — GBP / USD / EUR (FX bank-rail)

FX currencies use a nested bank + address envelope. country and address.street / address.city / address.zip_code are required for all three; per-currency identifiers differ.

currencystringRequired

GBP / USD / EUR.

namestringRequired

Recipient name.

countrystringRequired

ISO 3166-1 alpha-2 country code.

typestringConditional

individual / business. Required for USD.

addressobjectRequired

street, city, zip_code required for all FX currencies; state additionally required for USD.

bankobjectRequired

Per-currency identifiers: GBP requires bank.account_number + bank.sort_code; USD requires bank.method (ach / wire), bank.account_number, bank.routing_number, bank.account_type (checking / savings), and optional bank.swift_code (used for wire); EUR requires bank.iban + bank.bic_code.

external_referencestring

Optional. Your own id, echoed back and usable as a lookup key. Available on any currency.

FX beneficiaries require the international-accounts feature (feature_not_enabled if it’s off). They do not require an active international account in that currency — a beneficiary is a sender-agnostic saved recipient, so whether funds can actually move is decided by the sender at payout time, not at recipient-save time. (Changed 2026-06-08; previously this also required a merchant virtual account.) external_reference is an optional field on any currency — your own id, echoed back and usable as a lookup key.

Body parameters — CAD-Interac

CAD beneficiaries require all three Interac fields. Bank fields are not used for CAD.

currencystringRequired

CAD.

namestringRequired

Recipient name.

interac_emailstringRequired

Interac e-Transfer email.

interac_first_namestringRequired

Recipient first name.

interac_last_namestringRequired

Recipient last name.

⚠️

Identity fields are immutable. You can’t change account_number, bank_code, currency, interac_email, etc. via PATCH. To change the destination account, soft-delete the old beneficiary and create a fresh one.

Idempotency semantics

When a beneficiary already exists for the same (merchant, currency, env, bank_code, account_number) (or (merchant, currency, env, interac_email) for CAD), the response includes a created flag:

ScenariocreatedrestoredHTTP
Fresh rowtrue(omitted)201
Existing active rowfalse(omitted)200
Restored from archivefalsetrue200
Existing row is blacklistedrefusedrefused400 beneficiary_blacklisted

The response is the Beneficiary object extended with the created (and optional restored) flag(s) per the idempotency table above.

Request — by currency
{
  "currency": "NGN",
  "name": "JANE DOE",
  "account_number": "0690000032",
  "bank_code": "044",
  "bank_name": "Access Bank",
  "email": "recipient@example.com",
  "phone": "+2348012345678"
}
Example
curl https://api.swappr.me/api/v1/beneficiaries \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "NGN",
    "name": "JANE DOE",
    "account_number": "0690000032",
    "bank_code": "044"
  }'

GETList beneficiaries

GET/v1/beneficiaries

Excludes soft-deleted rows by default. To list deleted rows, use the dashboard.

Query parameters
limitinteger

1-100, default 50.

starting_afterstring

Cursor.

currencystring

Filter to one currency.

qstring

Search by name or account number (case-insensitive contains).

Request
curl 'https://api.swappr.me/api/v1/beneficiaries?currency=NGN' \
  -H "Authorization: Bearer sk_test_..."

GETRetrieve a beneficiary

GET/v1/beneficiaries/{id}

Returns the beneficiary including soft-deleted state. For deleted beneficiaries, the deleted_at field is populated and account_number shows the original (un-mangled) value.

Request
curl https://api.swappr.me/api/v1/beneficiaries/ckben_xxxxxxxxxxxx \
  -H "Authorization: Bearer sk_test_..."

PATCHUpdate a beneficiary

PATCH/v1/beneficiaries/{id}

Narrow scope — name, email, phone only. Identity fields immutable.

Body parameters
namestring

Recipient label.

emailstring

Contact email.

phonestring

Contact phone.

200 OK with updated Beneficiary.

Request
{
  "name": "Jane M. Doe",
  "email": "recipient.new@example.com",
  "phone": "+2348023456789"
}

DELSoft-delete a beneficiary

DEL/v1/beneficiaries/{id}

Marks the row deleted but preserves it for audit. The unique-tuple (merchant, currency, env, bank_code, account_number) is freed by mangling the stored account_number; the original is preserved in deleted_account_number for audit + retrieval.

You can re-add the same beneficiary after deletion — that creates a fresh row + leaves the deleted one in history.

Body parameters
reasonstring

Optional reason for the deletion.

200 OK with a beneficiary_delete_result. was_already_deleted is true if you DELETE the same beneficiary twice — the call is idempotent.

Request (optional reason)
{
  "reason": "No longer paying this vendor"
}
Response
{
  "object": "beneficiary_delete_result",
  "id": "ckben_xxxxxxxxxxxx",
  "deleted": true,
  "was_already_deleted": false
}
200 OK