One roof, several cabinets, several drawers
Think of the deployment as a back office with a wall of filing cabinets. Each cabinet belongs to one regulated institution: its files never mix with another institution’s, because a regulator audits each institution on its own. Inside a cabinet, drawers separate lines of business — one for the wallet product, one for the credit product. On the wall hangs an index card saying which cabinets and drawers exist. A clerk files money movements only into a drawer the index names; a movement addressed to a drawer that is not on the index is handed back, never filed somewhere “close enough”. The rail’s vocabulary maps one to one:
Two consequences fall straight out of the model:
- Every posting carries its scope. Each transaction is written into the organization and ledger it resolved to, so each institution’s records are complete on their own — regulatory and accounting reports come out per institution without untangling a shared book.
- The rail never guesses a scope. A movement whose organization and ledger cannot be proved against the catalog is refused before any posting. Booking money into the wrong institution’s ledger is the one mistake this design exists to make impossible.
The toggle: an empty key means off
The catalog key starts empty, and empty is the not provisioned sentinel: the tenant posts only into the organization and ledger its binding names, exactly as before the key existed. Writing a catalog turns the feature on; the pair the binding names remains the default — the scope every flow posts in unless it resolves another one.
The default organization and ledger are never in the catalog. Their accounting routes stay in the
routing.* keys and their asset and clearing account in their own configuration, exactly as Setting up the rail provisions them. The catalog declares only what the binding does not: repeating the default ledger there would give one book two sources of truth, so the rail refuses a catalog that redeclares it.Activate it, step by step
1
Complete the ordinary setup first
The catalog extends a working deployment; it does not replace the setup. Run Setting up the rail end to end: the binding you write there makes your institution the default participant and its ledger the default book. If your deployment is one institution with one book, stop there — with the catalog key empty, nothing on this page changes anything.
2
Provision the additional book in Midaz and the CRM
Each additional ledger is provisioned with the same recipe the setup page walks, pointed at the new book instead of the default one:
- The organization and the ledger. An additional ledger of your own institution goes under your existing organization. An additional participant (multi-tenant) gets an organization of its own, because one Midaz organization is exactly one regulated institution.
- The asset and the accounts on the new ledger, including its own external clearing account — an alias such as
@external/BRLis unique within a ledger, so each book needs its own. - The operation routes on the new ledger, one credit-and-debit pair per money profile the book serves. Unlike the default ledger, these UUIDs do not go into the
routing.*keys — they go inside the catalog document in the next step. - The holder records and aliases in the CRM, addressed with the
X-Organization-Idof the organization that owns the book. The alias’sledgerIdis what tells the rail which book an account banks in, so on an organization with more than one ledger every alias must name its ledger.
3
Write the catalog
The catalog is written once, to On a single-tenant deployment the entry carries your own ISPB and your own organization id, and the ledgers are the additional books; the identifiers differ per participant only in multi-tenant.A successful write answers The key is hot-reloadable: write it with the app running and it takes effect on the running deployment, with no restart and no redeploy.
PUT /system/tenant_policy/organizations, with the same wrapping the binding uses: the body is {"value": ...} and value is a string whose content is a JSON document — a list with one entry per cataloged organization, each naming its participant’s ISPB, its Midaz organization, and that organization’s ledgers with their routes. Your own organization appears here when it carries additional ledgers — never re-declaring the default ledger — and an additional participant’s organization always does.This is the document, unwrapped, with placeholder identifiers:routes takes up to ten profiles — in, inQrCode, out, outReversal, intraPsp, intraPspReversal, medDebit, medCredit, pixautomaticoDebit, pixautomaticoReversal — each a pair of Midaz operation route UUIDs. Only in and inQrCode are mandatory: every catalog ledger exists to receive, so a book that can serve neither inbound leg is a provisioning mistake, not a narrower book. The other eight are optional; a flow that needs an absent one refuses with 409 PIX-0105, naming the profile, the organization, and the ledger — the same behavior an unset routing.* key produces on the default book.Let jq do the string escaping, as with the binding:204. Read the key back the way you read the binding back — the value returns as a quoted string carrying your document:4
Know what the validators refuse, and when
The catalog is validated twice, and the split matters: the write validator catches everything that can be judged from the document alone, so a malformed catalog is refused at the admin surface instead of discovered on a payment.Refused at write time, with
400 validation_error:- a
valuethat is not a string, or anything after the first JSON document; - an unknown field at any depth — a mistyped route-profile name included, so a typo cannot pass as a silently absent profile;
- an
ispbthat is not exactly 8 digits; anorganizationId,ledgerId, or route leg that is not a non-zero UUID; - an empty
externalAliasorasset; a route pair missing either leg; an organization declaring no ledger; - a missing
inorinQrCodeprofile on any ledger; - a duplicated
ispb,organizationId, orledgerIdanywhere in the document. A403is a missing permission, never a rejected value.
5
Understand how money routes once it is on
- Every transaction is recorded in its scope. The posting carries the organization and ledger it resolved to, so each institution’s book stays complete and auditable on its own.
- An inbound credit finds its own book. The receiver ISPB picks the organization; the account’s CRM record picks the ledger. An organization with a single ledger tolerates a record that names none — there is nothing to choose between; one with several does not, and refuses with
500 PIX-0120rather than guess. - An outbound order names the paying participant when the tenant is several.
POST /v1/transactionstakes an optionalpayerIspb. A tenant acting as one participant omits it and behaves exactly as before; a tenant acting as several must supply it — omitted is422 PIX-0127, and an ISPB the tenant does not act as is422 PIX-0128— because picking a payer arbitrarily would debit another institution’s customer. - Between two ledgers of the same organization, the transfer is internal. An alias is unique within a ledger, so it cannot be one posting: the rail reserves the amount on the payer’s ledger against its clearing account, posts the final credit on the payee’s ledger against its clearing account, then commits the reserve. Both legs book on each ledger’s own
intraPsproute pair, and each book stays internally double-entry. - Between two organizations of the same tenant, the payment is ordinary Pix. Two organizations are two regulated participants, so the money travels the settlement rail (SPI) exactly as a payment to any other institution would — same tenant is never “same book”.
- Each additional participant authenticates as itself. Orders that leave an additional organization are sent to JD under that participant’s own credential, provisioned per ISPB. A participant whose credential is not provisioned refuses its first order with
409 PIX-0092and releases the hold, and the readiness probe reports it down naming that ISPB before traffic reaches it.
Turning it off
Writing an empty string back to
tenant_policy/organizations is accepted and returns the tenant to default-only behavior — the same state as before activation. It does not undo anything already booked: money posted in an additional scope stays in that ledger, and it is no longer reachable through this rail — any flow that resolves to a scope the catalog no longer declares refuses with 500 PIX-0120 before touching the ledger. So empty the key only when nothing routes to the additional books anymore: no accounts whose records point at them, no in-flight payments, and no hosted participant mounted on an additional organization. To retire one book while keeping the feature, remove that ledger’s entry and leave the rest of the catalog in place — the same read-time proof applies, scope by scope.
Where to go next
Setting up the rail
The provisioning chain this page extends: the ledger objects, the CRM records, the routing keys, and the binding that names the default pair.
The direct and indirect model
How this axis differs from hosting indirect participants: an indirect is a position inside your book; an additional organization is a direct participant with books of its own.
Environment variables
The deploy-time configuration, and which values live in systemplane keys instead.
Pix JD error list
Every
PIX-NNNN code this page names, with its status and the detail text the response carries.
