Skip to main content
A single customer rarely has a single balance. The same person can hold a main account, a benefit account, a court-ordered or blocked account, a product sub-account, or a promotional balance — each with its own rules, its own statement, and its own reconciliation needs. The common shortcut is to treat these as labels on one account and sort them out in application code. That works until the day balances, ledgers, statements, or operational rules need to diverge — and then a single account can no longer tell the truth about where the money is. This page shows the reference architecture for one customer owning many Midaz accounts, how the platform’s native entities map to that model, and a step-by-step example of building three accounts for the same customer document.

Why this matters


For product and operations teams, modeling each balance as its own account means every segregation rule — a blocked outflow, a benefit-only spend, a promotional balance with an expiry — is enforced by the Ledger, not buried in application logic. Each account carries its own statement and reconciliation trail. For engineering teams, the customer’s external addresses (core banking numbers, payment-rail identifiers) resolve to a specific account before a transaction is posted. There’s no guessing which balance an incoming event belongs to, and no separate balance store to keep in sync with the Ledger.
One account with labelsMany accounts per customer
Balance “types” live in metadata or app codeEach balance is its own Account with its own ledger and statement
Blocking or restricting funds requires custom logicAccount Type and route rules enforce restrictions at the Ledger
Statements must be filtered and reassembled per balanceEach account produces a clean, independent statement
External identifiers all point to the same balanceEach external identifier resolves to a specific account
Reconciliation mixes unrelated movementsReconciliation is separated by account by design

The reference architecture


The model is one owner, N accounts, N external identifiers:
  • One owner — the customer, identified by a document (CPF, CNPJ, tax ID). The owner represents who holds the relationship. It does not carry a balance and does not decide transaction routing.
  • N accounts — each account is a self-contained accounting position, with its own balance, ledger, statement, and rules.
  • N external identifiers — the addresses other systems (a core banking platform, a payment rail) use to reach a specific account. Each identifier resolves to exactly one account.
A middleware layer keeps the map between external identifiers and accounts, and resolves each identifier to the correct account before calling Midaz. Midaz remains the source of truth for accounts, balances, and entries.
One customer, many accounts — reference architecture
Each external identifier is not just a nickname for one shared balance. When balance, ledger, statement, or operational rule differs by destination, each identifier must point to a distinct account.

How Midaz maps the model


Every part of this architecture maps to a native Midaz entity — you don’t need to build a separate balance store or invent an accounting layer.
ConceptMidaz entityWhat it does
The owner (customer)HolderIdentity behind the accounts (NATURAL_PERSON or LEGAL_PERSON), keyed by document. Holds no balance.
Each balance positionAccountSource of truth for balance, entries, and statement.
The nature of each balanceAccount TypeClassifies an account (main, benefit, blocked) and enables route validation.
All accounts of one customerPortfolioGroups a customer’s accounts to view the total relationship.
External address → accountAccount alias + entityIdThe alias is how transactions address an account; entityId links it to an external system’s identifier.
Banking and regulatory contextAlias Account (CRM)Attaches branch, account number, and regulatory fields, linking a Holder to a specific account.
Much of what an external “alias registry” would do is already native: the account alias is the in-Ledger address, and entityId stores your external system’s identifier. The middleware’s job is narrower than it first appears — translate an external rail identifier into the right account alias, then post.

Prerequisites


This example assumes a running Midaz environment with the following in place:
RequirementDetails
Midaz (v3.x.x+)Core Ledger with an Organization and Ledger already created
A registered assetBRL registered as the operating asset in the Ledger
Account Type validationEnabled per-Ledger so each account’s nature is enforced (see Step 1)
CRM (optional)Running if you want to attach identity, banking, and regulatory context
Values in Midaz are represented in the smallest unit of the currency. For BRL, 15000 means R$ 150.00 (centavos).

Building three accounts for one customer


The customer with document 12345678900 needs three accounts: a main account for ordinary movement, a benefit account governed by product rules, and a blocked account that accepts inflows but restricts outflows.
1

Enable Account Type validation

Turn on validation so every account must declare a registered type. This is what lets the Ledger enforce each account’s nature.
PATCH /v1/organizations/{org_id}/ledgers/{ledger_id}/settings

{
  "accounting": {
    "validateAccountType": true
  }
}
Settings take effect immediately — no redeployment needed.
2

Register the Account Types

Create one Account Type per balance nature. The keyValue is what each account’s type field must match.
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/account-types

{
  "name": "Main Account",
  "description": "Ordinary account, free for regular movement",
  "keyValue": "main_account"
}
Repeat for benefit_account (“Movement governed by product rules”) and restricted_account — the blocked account, where inflows are allowed but outflows are conditioned or blocked.
3

Create the three accounts

Each account is linked to the BRL asset, declares its type, and carries an alias (its in-Ledger address) and an entityId (its identifier in your external system).
POST /v1/organizations/{org_id}/ledgers/{ledger_id}/accounts

{
  "name": "Main account — 12345678900",
  "assetCode": "BRL",
  "alias": "@cust_12345678900_main",
  "entityId": "0001/12345-1",
  "type": "main_account"
}
Create the benefit account with alias @cust_12345678900_benefit, entityId 0001/88888-2, type benefit_account; and the blocked account with alias @cust_12345678900_blocked, entityId 0002/77777-0, type restricted_account.
The entityId is where you store the external address that other systems use to reach this account — your map between Midaz and your source system.
4

Register the customer as a Holder

Create one Holder for the customer. The same Holder will own all three accounts, keeping identity centralized.
CRM runs as a separate service, and every request requires the X-Organization-Id header. See Getting started with CRM for service setup and the full schema.
curl -X POST http://localhost:4003/v1/holders \
  -H "Content-Type: application/json" \
  -H "X-Organization-Id: <your-organization-id>" \
  -d '{
    "type": "NATURAL_PERSON",
    "name": "Jane Smith",
    "document": "12345678900",
    "contact": {
      "primaryEmail": "jane.smith@example.com"
    }
  }'
Save the returned holderId — you’ll use it in the next step.
5

Link each account to the Holder

Create an Alias Account per ledger account to attach banking and regulatory context. This is what powers CRM-driven features and keeps customer-facing details separate from the Ledger.
curl -X POST http://localhost:4003/v1/holders/<holder-id>/aliases \
  -H "Content-Type: application/json" \
  -H "X-Organization-Id: <your-organization-id>" \
  -d '{
    "ledgerId": "<your-ledger-id>",
    "accountId": "<main-account-id>",
    "bankingDetails": {
      "branch": "0001",
      "account": "12345",
      "type": "CACC",
      "countryCode": "BR"
    },
    "metadata": {
      "purpose": "main"
    }
  }'
Notice how the main account’s entityId (0001/12345-1) decomposes into the branch (0001) and account (12345) you record here — that’s the external address resolving to one specific account. Repeat for the benefit and blocked accounts, pointing accountId at each one.
The result: one customer, three accounts, three distinct external addresses — each with its own balance, statement, and rules, all owned by a single Holder.
OwnerExternal identifierMidaz accountUseTreatment
123456789000001/12345-1@cust_..._mainMain accountFree for ordinary movement
123456789000001/88888-2@cust_..._benefitBenefit accountMovement governed by product rules
123456789000002/77777-0@cust_..._blockedCourt-ordered / blockedInflow allowed, outflow conditioned or blocked

The transaction boundary


When an external event arrives, the resolution happens before Midaz is called. Keeping each layer in its lane is what preserves accounting clarity.
1

External event

A transaction, query, or settlement arrives carrying an external identifier.
2

Middleware resolves the identifier

The middleware looks up the external identifier and resolves it to the correct Midaz account alias, applying status validation (active, blocked, closed).
3

Midaz posts to the right account

Midaz records the entry against the resolved account, preserving balance and ledger. Statement and reconciliation stay separated by account.
LayerResponsibilityShould not do
CRM / registrationHold the commercial and identity view of the customer, including the link between document and relationship.Transaction routing, destination decisions, or settlement rules.
MiddlewareResolve external identifiers to a Midaz account before the transaction, and apply status validation.Invent balances, duplicate accounting, or depend on the CRM in real time for routing.
MidazRecord accounts, balances, entries, ledgers, and statements as the financial source of truth.Know external-rail details beyond the identifiers needed for integration.
An identifier for a blocked or closed account should fail validation before Midaz is called. Routing decisions belong in the middleware; the Ledger stays the source of truth for balances and entries.

What this unlocks


  • Real segregation — each balance has its own ledger and statement, so a blocked balance can never be spent through the main account by accident.
  • Unambiguous routing — every external event has a single, well-defined destination account.
  • Centralized identity — one Holder owns many accounts; identity and contact data live in one place while balances stay separate.
  • Native, not bolted-on — accounts, types, aliases, and entityId are platform primitives, so there’s no parallel balance store to reconcile against the Ledger.

What you need to get started


RequirementDetails
Midaz (v3.x.x+)Organization, Ledger, and a registered asset
Account Type validationEnabled per-Ledger via the Ledger Settings API
Account TypesOne per balance nature (main, benefit, blocked, …)
AccountsOne per balance, each with an alias and an entityId
CRM (optional)A Holder per customer plus an Alias Account per ledger account

Next steps


Accounts

The core financial unit — aliases, entityId, and external accounts.

Account Types

Classify accounts and enforce their nature with route validation.

Portfolios

Group a customer’s accounts to view the total relationship.

CRM: Holders & Alias Accounts

Centralize identity and attach banking and regulatory context.