Corebanq Public Docs
Counterparties

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.

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_accounts on reads.

It does not support mutating own_accounts inline. Any non-empty own_accounts payload is currently rejected with 400.

Target model

EntityRole
counterpartyOrchestration 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.
recipientInternal 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 id or has no payment sources at all).

Core graph

Important rules

  • counterparty is the participant root entity.
  • contacts stay directly linked to the counterparty.
  • canonical email and phone data live in contacts; counterparties do not expose top-level email or phone fields.
  • 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_accounts are read-only in this module; mutate them in the dedicated accounts flows.
  • list endpoints follow the shared CoreBanq getAll contract.
  • 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_id for regular counterparties and for inline recipient-backed source creation
  • optional mirror-link: set customer_id only when the counterparty represents that same customer entity; when set, customer_id must equal owner_id and is not used as the create scope
  • visibility contract: provide and read is_visible_to_others as the authoritative visibility flag; display_type is no longer returned in counterparties API responses
  • optional: type, alias, reference, avatar_id, contacts, bank_accounts, crypto_wallets, metadata
  • supported type values: 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,
			"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:

  • limit
  • offset
  • sort
  • filter (JSON object)
  • search._text
  • search.

Reserved text search supports the shared getAll operators such as .like, .start_with, and .end_with. For counterparties, _text searches both root counterparty fields and nested aggregate data from:

  • contacts
  • bank_accounts
  • crypto_wallets
  • own_accounts
  • addresses
  • payers

Common search fields for counterparties:

  • search.id
  • search.name
  • search.alias
  • search.reference
  • search.display_type
  • search.type
  • search.customer_id
  • search.owner_id
  • search.is_visible_to_others
  • search.contacts__type
  • search.contacts__value
  • search.bank_accounts__iban
  • search.bank_accounts__bic
  • search.bank_accounts__bank
  • search.bank_accounts__is_primary
  • search.bank_accounts__is_validated
  • search.crypto_wallets__wallet_address
  • search.crypto_wallets__network
  • search.crypto_wallets__network_id
  • search.crypto_wallets__currency_id
  • search.crypto_wallets__is_primary
  • search.own_accounts__iban
  • search.own_accounts__currency
  • search.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:

  • contacts
  • bank_accounts
  • crypto_wallets
  • own_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:

  • name
  • type
  • alias
  • reference
  • avatar_id
  • active (set false for soft delete / deactivate)
  • is_visible_to_others
  • metadata

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:

  • contacts
  • bank_accounts
  • crypto_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 id keeps or updates an existing linked record;
  • for bank accounts, an item without id but with an IBAN matching an existing bank account on the same counterparty keeps / updates that existing account;
  • an item without id creates a new recipient-backed record inline;
  • own_accounts remains unsupported on patch and returns 400 when 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_id
  • type
  • value

Optional fields:

  • validated
  • preferred
  • metadata

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_number
  • number
  • region_code

List Contacts

GET /v1/counterparties/contacts

Returns contacts in the same shared getAll response shape.

Common search fields for contacts:

  • search.id
  • search.counterparty_id
  • search.type
  • search.value
  • search.validated
  • search.preferred
  • search.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:

  • type
  • value
  • validated
  • preferred
  • metadata

If the effective contact type is phone and the value changes, the phone-derived metadata is refreshed automatically.

  • 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.

On this page