POST /v1/indirects. The direct and indirect model explains where an indirect participant’s money lives: the settlement account at the Banco Central, the limit at JD, and the position in your ledger.
This page explains what a hosted participation is and how it behaves. Hosting indirect participants covers the provisioning: the delivery-secret encryption key, the hosting posture, and the registration call. Its numbering continues from the setup page. Of those steps, only the encryption key refuses every registration on its own, so provision it first. The hosting posture gates the money paths, not the registry, so you can register participants before you turn it on.
Who is responsible for what
Most operational mistakes here come from a search for the answer in the wrong column.
In practice, a Pix for a customer of an indirect participant stops with you. It lands in an account that represents that institution’s position. You do not know, and cannot know, which of its customers owns the money. The indirect participant credits its customer in its own core, after it receives your notice.
What registering one creates
A registration does more than write a row. The plugin also creates that institution’s settlement position in Midaz. That is a ledger account whose alias starts with
@pi_ and ends with the institution’s ISPB, the eight-digit code that BACEN gives to every participant. An indirect on ISPB 12345678 settles at @pi_12345678.
That position holds that institution’s money. The response returns it as piAccountAlias. The plugin derives the alias from ispb. You never choose it, and a client never creates the account.
One position per institution is the whole point. A credit for one indirect lands only on that indirect’s account. One institution’s money therefore never mixes with another’s, and never with your own. The registry exists to give that segregation.
The alias uses an underscore, not a slash:
@pi_12345678. Midaz rejects a slash in a user alias, so a path-shaped name resolves to nothing.Before you register anyone
Indirect participation is off by default. The per-tenant systemplane flag
plugin-br-pix-jd.indirects/enabled must be on, and its default value is false.
The flag gates two halves, and they behave differently:
- The management API works with the flag off. You can register participants, list them, and read one before enablement. Onboarding before go-live is deliberate.
- No money moves with the flag off. Resolution decides which indirect an incoming credit belongs to. With the flag off, it reports the feature as disabled. No inbound credit goes to a
@piaccount, and no indirect can originate an outbound order.
422 PIX-0111. One is the own-QR-certificate write on PATCH /v1/indirects/{indirectId}. The other is GET /v1/indirects/{indirectId}/jwks.
The read fails closed. When a money path “goes back to direct behavior” and nobody changed the key, this is the cause. Look at the systemplane.
Registering one
1
Turn the feature on for the tenant
Set
plugin-br-pix-jd.indirects/enabled to true. Registration works without it, but nothing settles until it is on.2
POST /v1/indirects
Send the institution’s
name, its ispb, and a delivery block with endpointUrl and secret. The answer is 201. It carries indirectId, the identifier that every later call routes by, and piAccountAlias.3
Nothing to poll: a 201 means ready
Every check runs before the plugin writes the registry row. First comes the ISPB uniqueness check, then the optional JD directory check, then the creation of the
@pi_{ispb} account in Midaz. Only then does the plugin write the row, with status ACTIVE. There is no intermediate state and nothing to wait for.4
On a failure, fix the cause and POST again
A failed step answers a coded error that names the step. The plugin writes no registry row, so the ISPB stays free. Fix the cause and send the POST again. That is the recovery. There is no separate retry route, because no half-written row remains to resume.
201 means ready, and a failure leaves no registry row. On a 201, status is always ACTIVE. Do not write a client that polls until the participant becomes routable. That loop waits for a change that cannot happen.
A failure can leave one thing behind: the settlement account, when that step succeeded and a later one failed. The plugin does not roll it back, and it does no harm. Nothing routes to a @pi account without an ACTIVE registry row that points at it. The account creation is idempotent by alias, so your next attempt adopts the same account and does not make a second one. A failure never leaves a registry row or a held ISPB.
The response still carries a provisioning object with a failedStep field. It is legacy and always null for every registration of this service, because a failed registration leaves no row.
The lifecycle states are ACTIVE, SUSPENDED, and CLOSED.
How money reaches an indirect participant
Follow one inbound Pix. The indirect calls no endpoint in this path. Your plugin does the work.
- “Can this account receive?” Before the Banco Central moves the money, it asks if the destination account accepts the credit. The question comes to you, because the indirect participant has no line to the SPI.
- A Pix arrives for an account at the indirect institution. It reaches your direct participation through the plugin’s cash-in webhook.
-
Resolution finds the indirect from the ISPB in the receiver block, in the participation registry. There are four outcomes, and only the first one credits an indirect:
- The ISPB matches an
ACTIVEparticipation. The credit posts to that participation’s@pi_{ispb}alias. - The ISPB is your own. It is your book, and the CRM resolves the account as usual.
- The ISPB matches nothing, or a suspended or closed participation. The plugin refuses the credit with
404 PIX-0119. It tells JD that it does not serve that receiver. It stores nothing, and no money settles. - The resolution fails on infrastructure. The plugin neither credits nor refuses. It answers an error, so that JD delivers the credit again. It fails closed on purpose and guesses nothing about money.
- The ISPB matches an
-
The credit lands on
@pi_{ispb}. The plugin records the movement against that settlement position and links its own transaction record to the indirect. It records the notice in the same database transaction as the credit. Either both exist, or neither does. -
The plugin pushes a signed notice to the
delivery.endpointUrlthat you registered, with JD’s payload byte for byte. The plugin calls the institution, not the reverse. - The indirect participant credits its own customer, in its own core.
Delivery is at-least-once. The plugin can send the same notice again after a retryable failure, or after a crash between the send and the bookkeeping. The
endToEndId stays the same across attempts. A receiver must deduplicate by endToEndId and treat a repeated payload as already handled.
The stored
secret is write-only. Every read of an indirect returns delivery.secret as ***, so a lost secret is replaced with a PATCH, never recovered.When the notice never arrives
The push can fail for good. The retries run out, or the endpoint answers
4xx. After that, the plugin marks the notice invalid and never tries it again. The money is correct either way. The result is narrow but serious: the movement is real, and the institution does not know about it.
GET /v1/indirects/{indirectId}/transactions closes that gap. It returns the movements that settled on one indirect’s @pi_{ispb} account. It is therefore the authoritative record, not the notice. An institution that reads it on a schedule no longer depends on the delivery. It is also the only way to read a window again after an outage of its own.
List an indirect participant’s transactions gives the rules that make a reconciliation correct. They cover the required half-open since window, integer centavos, oldest-first cursor paging, and the silent clamp on limit. A read of the feed changes nothing and sends no notice again.
Returning a Pix that an indirect participant received
A devolução of a Pix that landed on an indirect participant’s position comes from that indirect. Send
POST /v1/refunds with the indirectId of that participant. The devolução then debits its @pi_{ispb} position.
The indirectId must name exactly the participant that received the original Pix. The plugin answers 422 PIX-0135 and moves no money when:
- the original Pix landed on an indirect’s position, and the refund has no
indirectId. - the refund names another indirect participant.
- the original Pix landed on a direct account, and the refund has an
indirectId.
indirectId then passes the same checks as on a payment order: 422 PIX-0111 when the feature is off, 404 PIX-0095 for an unknown participant, and 422 PIX-0100 when the participation is not ACTIVE.
Hosting the QR code under the institution’s own certificate
By default, the plugin publishes an indirect’s dynamic QR code under your direct participation. An indirect can publish its own instead. Set
qrCertificate.ownCertificate with a publicBaseUrl on PATCH /v1/indirects/{indirectId}. The institution then serves the signed document from its own host.
The institution cannot make the validation keys itself. JDPI holds the certificate and does the signing. GET /v1/indirects/{indirectId}/jwks returns the JWK Set for that indirect. The institution publishes it on its own host, and payer PSPs validate against it. Fetch it when you configure the certificate, and again after each rotation.
Lifecycle status deliberately does not gate that read. A suspended or closed indirect still has live QR codes in circulation. Without the key, their validation would break.
Reading the registry
status and ispb (8 digits, exact). List indirect participants gives the cursor-paging rules and the silent clamp on limit.
A GET by id, and the list, always show the current truth. The money paths read a cached projection. Every replica can take up to the resolution cache TTL to see a suspension. The TTL is 30 seconds by default. The ledger lock from suspend applies at the moment of the call.
Suspending, reactivating, and closing
PATCH /v1/indirects/{indirectId} carries at most one lifecycle action: suspend, reactivate, or close. Each one writes the ledger as well as the registry.
suspendstops both sending and receiving on the indirect’s@piaccount. The ledger itself refuses that participant’s money. A suspension therefore holds even on a replica whose resolution cache is stale.reactivatestarts both again.closeis permanent.CLOSEDis terminal, and after it the plugin refuses every field change on the row too.
@pi account for good but keeps it. A later registration of the same ISPB reuses that account. The account is unblocked, active again, and keeps its accounting history.
A PATCH with only fields and no action changes those fields. An empty PATCH changes nothing and returns the current row. It is not an error.
What goes wrong
The operation pages list every refusal of this registry, with its status and exact condition. They also say what the plugin wrote or did not write when it refused. See Register an indirect participant, Update an indirect participant, Get an indirect participant, List indirect participants, Get an indirect participant’s JWK Set, and List an indirect participant’s transactions. The Pix JD error list has every code, with the exact
detail of each one.
The status tells you whether to retry, not the code family.
PIX-0107 and PIX-0123 are the same condition, seen two ways. A 409 means that the rail read the configuration and found that the key does not exist. Only an operator can change that, so a retry loops forever. A 503 means that the read did not complete. The rail does not know that the key is absent, so a retry is right. Neither one stores anything.Where to go next
- Hosting indirect participants: the provisioning, with the encryption key, the posture, and the registration call.
- Register an indirect participant: the full request contract and every refusal.
- List an indirect participant’s transactions: the reconciliation feed, with the full paging rules.
- Update an indirect participant: field changes, lifecycle actions, and the own-certificate configuration.
- Direct Pix via JD: the direct participation that all of this rides on, and how settled movements land in Midaz.

