Description
Counterparties & Contacts
The counterparties module manages reusable payment participants, their direct contact records, and the payment-source projections exposed through the normalized counterparty graph.
Purpose and use
Counterparties let users maintain people, businesses, bank accounts, crypto wallets, and own-account destinations that may be used in transfer and invoice workflows. They reduce repeated beneficiary entry and keep payment-source ownership clear.
Who uses this. Payment operations, customer support, relationship managers, finance teams, and customers use counterparties when preparing or investigating payment destinations.
How it works. A counterparty owns contacts and links payment sources. Bank accounts and crypto wallets can be linked by ID or created inline through a recipient-backed flow so the transfer layer can still route payments through recipient records.
What users do. Users create counterparties, add contacts, link bank accounts or wallets, review own accounts, update metadata, and choose a counterparty destination during payment setup.
Outcomes and side effects. Counterparty changes affect future payment selection and beneficiary audit history. They do not alter completed transfers or ledger postings.
Related manuals: Recipients, Transfers, Invoices, Customers.
This module is the orchestration root for counterparty data. It can:
- create and update counterparties;
- create direct contacts or reassign existing contacts by ID;
- link existing bank accounts and crypto wallets by ID;
- create new bank accounts and crypto wallets inline through a recipient-backed flow;
- expose
own_accountson reads.
It does not support mutating own_accounts inline. Any non-empty own_accounts payload is currently rejected with 400.
Target model
| Entity | Role |
|---|---|
counterparty | Orchestration root. The only entity API consumers create, update, and address by ID. It owns contacts, controls which bank accounts and crypto wallets are linked, and exposes own_accounts in read responses. |
recipient | Internal execution entity. A backing row in recipients.recipients that is created lazily (once per counterparty) the first time an inline bank account or crypto wallet is created. It exists solely to satisfy the FK requirements of recipients.bank_accounts and crp.crp_recipients. It is never surfaced in the counterparties API response and must not be addressed directly by callers. |
The separation means:
- Callers think in terms of counterparties and their payment sources.
- The payment-routing layer continues to operate on recipients without any change.
- A counterparty may exist without a backing recipient (e.g. when it only links existing payment sources by
idor has no payment sources at all).
Core graph
Important rules
counterpartyis the participant root entity.contactsstay directly linked to the counterparty.- canonical email and phone data live in
contacts; counterparties do not expose top-levelemailorphonefields. - bank accounts and crypto wallets remain recipient-backed even when created from counterparties endpoints.
- inline bank / crypto creation ensures a backing recipient exists with an empty billing address placeholder before the recipient is inserted.
own_accountsare read-only in this module; mutate them in the dedicated accounts flows.- list endpoints follow the shared CoreBanq
getAllcontract. - read access is RBAC-scoped and customer isolation is enforced by the runtime.
- standalone contact creation requires parent counterparty update access and is executed transactionally.
Endpoints
Create Counterparty
POST /v1/counterparties
Creates a new counterparty within an owner customer scope.
In the same request you may:
- create direct contacts inline or reassign existing contacts by
id; - link existing bank accounts and crypto wallets by
id; - create new bank accounts and crypto wallets inline — those records are still created through a recipient-backed flow;
- persist arbitrary
metadata.
You may not mutate own_accounts here; any non-empty own_accounts array returns 400.
Nested contact objects support only id, type, value, and metadata. Use the standalone contacts API when you need to set validated or preferred.
Request highlights
- required:
name,owner_id,is_visible_to_others - ownership scope: provide
owner_idfor regular counterparties and for inline recipient-backed source creation - optional mirror-link: set
customer_idonly when the counterparty represents that same customer entity; when set,customer_idmust equalowner_idand is not used as the create scope - visibility contract: provide and read
is_visible_to_othersas the authoritative visibility flag;display_typeis no longer returned in counterparties API responses - optional:
type,alias,reference,avatar_id,contacts,bank_accounts,crypto_wallets,metadata - inline
bank_accounts:activeandis_validateddefault totruewhen omitted; explicit values are preserved for create and patch/reconcile flows - supported
typevalues:business,personal
Example
{
"owner_id": "8c6f6a0f-0b6e-46f2-996a-0f35e72cf3d8",
"name": "Acme Corp",
"is_visible_to_others": true,
"type": "business",
"alias": "acme",
"reference": "REF-001",
"contacts": [
{
"type": "email",
"value": "finance@acme.example",
"metadata": {}
}
],
"bank_accounts": [
{
"iban": "CH9300762011623852957",
"bank": "Acme Treasury Bank",
"is_primary": true,
"active": false,
"is_validated": false,
"metadata": {
"source": "manual"
}
}
],
"metadata": {
"source": "manual"
}
}List Counterparties
GET /v1/counterparties
Returns counterparties in the standard project getAll envelope:
{
"data": [],
"total": 0,
"total_unfiltered": 0,
"has_more": false
}Each counterparty item keeps its public JSON shape stable: nullable scalar fields are returned explicitly as null, and collection fields are returned as empty arrays when there are no linked rows instead of being omitted from the payload.
Supported query pattern:
limitoffsetsortfilter(JSON object)search._textsearch.
Reserved text search supports the shared getAll operators .like, .eq, .start_with, and .end_with. For counterparties, _text searches both root counterparty fields and nested aggregate data from:
contactsbank_accountscrypto_walletsown_accountsaddressespayers
The .like operator also preserves aggregate phrase matching across adjacent fields within those surfaces, while .eq, .start_with, and .end_with match the concrete text fields inside the aggregate. Deactivated (active = false) bank accounts are never matched, whether via _text or search.bank_accounts__*.
Common search fields for counterparties:
search.idsearch.namesearch.aliassearch.referencesearch.display_typesearch.typesearch.customer_idsearch.owner_idsearch.is_visible_to_otherssearch.contacts__typesearch.contacts__valuesearch.bank_accounts__ibansearch.bank_accounts__bicsearch.bank_accounts__banksearch.bank_accounts__is_primarysearch.bank_accounts__is_validatedsearch.crypto_wallets__wallet_addresssearch.crypto_wallets__networksearch.crypto_wallets__network_idsearch.crypto_wallets__currency_idsearch.crypto_wallets__is_primarysearch.own_accounts__ibansearch.own_accounts__currencysearch.own_accounts__status
Useful ownership patterns:
- only the mirror-linked “self” counterparty:
search.owner_id.eq=+search.customer_id.eq= - all owner-scoped counterparties except that mirror-linked self row:
search.owner_id.eq=+search.customer_id.ne=
search.customer_id.ne is null-safe for counterparties list filtering. Regular owner-scoped counterparties usually keep customer_id = null, and those rows are treated as “not equal” to a concrete customer UUID instead of being dropped by SQL null comparison semantics.
Get Counterparty by ID
GET /v1/counterparties/{id}
Returns the full counterparty aggregate, including:
contactsbank_accountscrypto_walletsown_accounts(read-only from this module)
Nullable scalar response fields stay present as null, and aggregate collections stay present as empty arrays when no linked records exist. Nested payment sources also keep products_allowed present as an array; it is empty when no product matrix projection has been attached. Address responses include address_hash.
Patch Counterparty
PATCH /v1/counterparties/{id}
Partially updates mutable scalar fields such as:
nametypealiasreferenceavatar_idactive(setfalsefor soft delete / deactivate)is_visible_to_othersmetadata
owner_id is the ownership / access-customer scope and is immutable through this generic PATCH endpoint. Sending owner_id in a patch request is rejected with 400; ownership transfer must be handled by a dedicated flow.
Counterparties responses no longer expose display_type; write requests and read responses use is_visible_to_others. For backward-compatible querying, search.display_type remains available as a derived filter.
Entity arrays use full-replace semantics when they are present in the payload:
contactsbank_accountscrypto_wallets
Rules:
- omit an entity array to leave that entity type unchanged;
- send an empty array to remove all linked entities of that type;
- for bank accounts / crypto wallets, an item with
idkeeps or updates an existing linked record; - for bank accounts, an item without
idbut with an IBAN matching an existing bank account on the same counterparty keeps / updates that existing account; - an item without
idcreates a new recipient-backed record inline; own_accountsremains unsupported on patch and returns400when provided.
IBAN reuse is checked across counterparties that share the same owner_id; counterparties under different owners may use the same IBAN. During patch, the target counterparty itself is excluded from this owner-scope check so unchanged bank-account payloads do not conflict with themselves.
As with create, nested contact payloads do not expose validated / preferred; use the standalone contacts API for those fields.
Contacts API
Create Contact
POST /v1/counterparties/contacts
Creates a contact linked to an existing counterparty.
Required fields:
counterparty_idtypevalue
Optional fields:
validatedpreferredmetadata
The service verifies parent counterparty update access before the insert is allowed and performs the write in a transaction.
When type = phone, metadata is normalized with phone-derived fields such as:
country_numbernumberregion_code
List Contacts
GET /v1/counterparties/contacts
Returns contacts in the same shared getAll response shape.
Common search fields for contacts:
search.idsearch.counterparty_idsearch.typesearch.valuesearch.validatedsearch.preferredsearch.active
Get / Update / Delete Contact
GET /v1/counterparties/contacts/{id}
PUT /v1/counterparties/contacts/{id}
DELETE /v1/counterparties/contacts/{id}
Contacts are addressed as standalone records after creation, but remain logically attached to their parent counterparty.
The update endpoint accepts partial changes for:
typevaluevalidatedpreferredmetadata
If the effective contact type is phone and the value changes, the phone-derived metadata is refreshed automatically.
Bank Accounts API
Create Counterparty Bank Account
POST /v1/counterparties/bank-accounts
Creates a recipient-backed bank account and links it to an existing counterparty.
Required fields:
counterparty_idiban
Optional fields:
bicbankis_primaryactive(defaults totruewhen omitted; explicit values are preserved)is_validated(defaults totruewhen omitted; explicit values are preserved)intermediate_bicintermediate_bankintermediate_routemetadata
The service verifies parent counterparty update access, ensures the backing recipient exists, creates the bank account, links it through cp_accounts, and grants record access.
{
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"iban": "CH9300762011623852957",
"bic": "POFICHBEXXX",
"bank": "PostFinance",
"is_primary": true,
"active": false,
"is_validated": false,
"metadata": {
"currency": "CHF"
}
}List Counterparty Bank Accounts
GET /v1/counterparties/bank-accounts
Returns counterparty bank accounts in the shared getAll response shape.
Common search fields:
search.idsearch.counterparty_idsearch.ibansearch.bicsearch.banksearch.is_primarysearch.is_validatedsearch._text.ilike
Deactivated bank accounts (active = false) are always excluded from results, so search.active is not offered as a filter here.
{
"data": [
{
"id": "34bd62b1-64f9-4e68-b77b-7cd52baf8077",
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"recipient_id": "e3a5cf19-4d7b-4417-bc3a-206970aaf822",
"iban": "CH9300762011623852957",
"bic": "POFICHBEXXX",
"bank": "PostFinance",
"is_primary": true,
"is_validated": false,
"active": true,
"metadata": {
"currency": "CHF"
}
}
],
"total": 1,
"total_unfiltered": 1,
"has_more": false
}Get Counterparty Bank Account by ID
GET /v1/counterparties/bank-accounts/{id}
Returns one bank account if the caller has record read access.
{
"id": "34bd62b1-64f9-4e68-b77b-7cd52baf8077",
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"recipient_id": "e3a5cf19-4d7b-4417-bc3a-206970aaf822",
"cp_account_id": "ecdb4436-3ab3-4c3d-8b1d-0603d4e2833a",
"iban": "CH9300762011623852957",
"bic": "POFICHBEXXX",
"bank": "PostFinance",
"is_primary": true,
"is_validated": false,
"active": true,
"metadata": {
"currency": "CHF"
}
}Update Counterparty Bank Account
PATCH /v1/counterparties/bank-accounts/{id}
Partially updates mutable bank-account fields. Omitted fields remain unchanged; an empty update returns the current record.
{
"bank": "PostFinance Treasury",
"is_primary": true,
"metadata": {
"currency": "CHF",
"source": "manual"
}
}Delete Counterparty Bank Account
DELETE /v1/counterparties/bank-accounts/{id}
Soft-deletes the bank account: the record is deactivated (active = false) rather than removed. Deactivated accounts are excluded from all subsequent list, get, update, and counterparty-aggregate reads, and no longer resolve by IBAN.
{}Crypto Wallets API
Create Counterparty Crypto Wallet
POST /v1/counterparties/crypto-wallets
Creates a recipient-backed crypto wallet and links it to an existing counterparty.
Required fields:
counterparty_idwallet_address- exactly one of
network_codeornetwork_id - exactly one of
currency_idorcrp_asset_id
Optional fields:
is_self_custodyis_primary
{
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"network_code": "ethereum",
"currency_id": "dc721b5d-8f7b-4c2a-9d4a-7ac8b4e6e7a1",
"wallet_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"is_self_custody": true,
"is_primary": true
}List Counterparty Crypto Wallets
GET /v1/counterparties/crypto-wallets
Returns counterparty crypto wallets in the shared getAll response shape.
Common search fields:
search.idsearch.counterparty_idsearch.wallet_addresssearch.networksearch.network_idsearch.currency_idsearch.crp_asset_idsearch.is_primarysearch.is_self_custodysearch.is_validatedsearch.statussearch.activesearch._text.ilike
{
"data": [
{
"id": "b6df7c36-1847-48aa-ae05-cdd02e81f53b",
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"recipient_id": "e3a5cf19-4d7b-4417-bc3a-206970aaf822",
"wallet_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"network": "ethereum",
"is_self_custody": true,
"is_validated": false,
"is_primary": true,
"status": "inactive",
"active": true
}
],
"total": 1,
"total_unfiltered": 1,
"has_more": false
}Get Counterparty Crypto Wallet by ID
GET /v1/counterparties/crypto-wallets/{id}
Returns one crypto wallet if the caller has record read access.
{
"id": "b6df7c36-1847-48aa-ae05-cdd02e81f53b",
"counterparty_id": "9cb28a4b-8f76-4a28-8e59-1068c502454d",
"recipient_id": "e3a5cf19-4d7b-4417-bc3a-206970aaf822",
"cp_account_id": "65e982bc-9260-4de7-a904-07353f4ad425",
"wallet_address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
"network": "ethereum",
"is_self_custody": true,
"is_validated": false,
"is_primary": true,
"status": "inactive",
"active": true
}Update Counterparty Crypto Wallet
PATCH /v1/counterparties/crypto-wallets/{id}
Partially updates mutable crypto-wallet fields. Omitted fields remain unchanged; an empty update returns the current record.
{
"network_code": "polygon",
"wallet_address": "0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71",
"is_primary": true
}Delete Counterparty Crypto Wallet
DELETE /v1/counterparties/crypto-wallets/{id}
Deletes the standalone counterparty crypto-wallet record and its counterparty source link.
{}Related artifacts
- OpenAPI source:
OpenAPI schema - Postman collection:
postman-collections/Corebanq - Counterparties.postman_collection.json
The Postman collection is maintained in the dedicated postman-collections repository, while the module documentation lives next to the module implementation inside modules/counterparties/docs.
Generate line chart data based on flexible query parameters
Returns counterparties visible to the requesting user in the standard Corebanq getAll response shape. For self-vs-others filtering, combine search.owner_id.eq with search.customer_id.eq or search.customer_id.ne; the not-equal variant is null-safe for nullable customer_id.