Reciprocity
How the Cumbuca Gateway API fulfils the Open Finance reciprocity duty, answering other participants' lookups about your account holders, in its two operating modes: without delegation, where Cumbuca stores the records and answers for you, and with delegation, where every lookup is proxied live, over mTLS, to a service you run yourself.
What is Reciprocity
Participating in Open Finance Brasil as a data receiver comes with a reciprocity duty: other participants can ask whether a given document belongs to one of your account holders, and read that customer's identification records. Cumbuca fulfils this duty in one of two ways, decided by a single instance-wide setting, namely delegation.
With and without delegation
Without delegation (delegation off), the default. Cumbuca stores the customer records it knows about (through the automatic registration that follows an authorised consent, or through the PUT /personal/identifications/register and PUT /business/identifications/register writes) and answers ecosystem lookups itself, from its own store. Your service is not called.
With delegation (delegation on). Cumbuca stores nothing. Every reciprocity lookup is proxied live, over mTLS, to a service that you implement and operate. It becomes the source of truth.
Comparison
| Without delegation | With delegation | |
|---|---|---|
| Where customer data lives | Stored at Cumbuca | At your service; nothing stored at Cumbuca |
| Who answers ecosystem lookups | Cumbuca, from its own store | Your service, one live call per lookup |
| Automatic post-consent registration | Performed (details) | Skipped (details) |
PUT /personal/identifications/registerPUT /business/identifications/register | 201: record stored | 422: always refused (details) |
| Endpoints your service exposes | None | Four, plus three more if you serve PJ customers (contract) |
| Availability dependency | None on your systems | An outage of your service fails the tenant's reciprocity lookups (details) |
Automatic Registration (without delegation)
When the instance does not delegate, Cumbuca itself needs to know your account holders in order to answer reciprocity lookups. It populates that store automatically, with no partner action, from the data-sharing consents your instance creates:
- When a data-sharing consent is created, Cumbuca starts tracking it.
- Open Finance does not push status changes, so Cumbuca polls the consent until it reaches
AUTHORISED(or expires, when the ecosystem auto-rejects unauthorised consents after ~1 h). - Once authorised, Cumbuca reads the customer's identifications through the consent (a billable read) and stores each record, with
x-data-source: internal. A personal consent feeds/personal/identifications; a business consent (one carrying abusinessEntity) feeds/business/identifications.
Mandatory permissions
Automatic registration is only possible if the consent carries the permission that authorises Cumbuca to read the customer's identification data. That is why, when the instance does not delegate, Cumbuca adds the matching permission automatically at consent creation, ensuring the reciprocity store can be populated:
| Consent | Permission added |
|---|---|
Personal (no businessEntity) | CUSTOMERS_PERSONAL_IDENTIFICATIONS_READ |
Business (carries businessEntity) | CUSTOMERS_BUSINESS_IDENTIFICATIONS_READ |
With Delegation: Your Service's Contract
When your instance delegates, you hand Cumbuca a base URL at onboarding. HTTPS is mandatory in production and homologation. Under that base URL, your service must implement the JSON POST endpoints below; the paths are relative to that base URL. The business (PJ) endpoints are only required if your institution serves legal-entity customers.
| Endpoint | Answers with | |
|---|---|---|
POST /check-account-holder | boolean | required |
POST /personal/identifications | array | required |
POST /personal/qualifications | object | required |
POST /personal/financial-relations | object | required |
POST /business/identifications | array | PJ only |
POST /business/qualifications | object | PJ only |
POST /business/financial-relations | object | PJ only |
data array of records and use "data": [] for "nothing to report". Qualifications and financial-relations answer with a single data object, and signal "no record for this document" with an HTTP 404 instead (see below).
- Every request Cumbuca sends is a JSON POST with
content-type: application/json. Documents travel only in the body, never in the query string. - Every request carries an
x-nexus-request-idheader (UUIDv7). Log it: it is also echoed as a response header on every Cumbuca gateway response, making it the cross-service correlation key for support. - Responses must be
HTTP 200with exactly the JSON shapes documented below. Wrong shapes are not tolerated anywhere in the chain; they fail the lookup (see error mapping).
| Header | Value |
|---|---|
content-type | application/json |
x-nexus-request-id | UUIDv7 correlation id; log it and include it in support requests |
POST/check-account-holder
The Cumbuca gateway asks whether the given document belongs to an account holder at your institution. This is an integration requirement, not the reciprocity data itself: it is an account-holder (titularidade) check the flow depends on, distinct from the identification endpoints below, which return the registration data actually shared under reciprocity. Your service must expose it to operate in Open Finance.
{
"cpf": "12345678901",
"cnpj": "12345678000199" // optional
}
| Field | Type | Notes |
|---|---|---|
cpf | string | required The logged user's 11-digit CPF (the natural person; for a legal entity, the representative's CPF) |
cnpj | string | optional The legal-entity document, present only when it accompanies the consent; always 14 digits |
cnpj field. The cpf field is always the logged user's 11-digit CPF; the CNPJ, when present, comes in the cnpj field (14 digits).
{
"data": { "isAccountHolder": true }
}
POST/personal/identifications
Cumbuca fetches the natural person's Open Finance personal-identifications records from your service.
{
"cpf": "12345678901"
}
{
"data": [
{ /* Open Finance Brasil personal-identifications record */ }
]
}
POST/business/identificationsoptional
Cumbuca fetches the legal entity's Open Finance business-identifications records from your service. Required only if your institution serves legal-entity (PJ) customers; institutions that serve natural persons only do not need to expose it.
{
"cnpj": "12345678000199"
}
{
"data": [
{ /* Open Finance Brasil business-identifications record */ }
]
}
POSTQualifications & financial relations
Four endpoints sharing one contract. Cumbuca fetches the customer's Open Finance qualification or financial-relations data from your service. Unlike identifications, each answers with a single object, not an array.
| Path | Body field | |
|---|---|---|
/personal/qualifications | cpf | required |
/personal/financial-relations | cpf | required |
/business/qualifications | cnpj | PJ only |
/business/financial-relations | cnpj | PJ only |
{
"cpf": "12345678901" // "cnpj" on the business paths
}
{
"data": { /* Open Finance Brasil qualification / financial-relations object */ }
}
404 when you have no record for the document. There is no empty-object equivalent of "data": [] here. Cumbuca recognises your 404 as "unknown document" and answers the requesting institution with 404, which is exactly what a non-delegated instance returns for a document it has never registered. Any other failure is treated as an outage on your side.
mTLS on the Delegated Leg
Every call from Cumbuca to your service rides on mutual TLS. There is no bearer token and no API key on this hop: the gateway's client certificate is the only authentication.
- What Cumbuca presents: a gateway client certificate on every delegated call. Your service must require a client certificate and validate / allowlist Cumbuca's; connections without it must be rejected. The certificate is exchanged during onboarding.
- What you must serve: a TLS server certificate that chains to a public CA. If yours doesn't, provide your CA chain to Cumbuca at onboarding so it can be added to the gateway's trust store.
- No plaintext fallback: in production and homologation, if the gateway's client certificate is unavailable Cumbuca refuses the call (
502 DELEGATED_UNAVAILABLE) rather than calling without mTLS, and a delegated instance will not even boot without the certificate configured.
Timeout Budget
- The gateway waits up to 30 seconds for your response: its configured receive timeout, which defaults to 30 s and may differ per deployment (the internal hop that precedes it has a fixed 30-second budget). Answer comfortably under that, ideally in well under a second, since these lookups sit on another institution's request path.
- Single attempt, no retry. A slow or failed answer immediately fails the lookup; the gateway will not try again.
Failure Semantics
- No fallback answer. When your service is down, times out, or answers a wrong shape, the reciprocity lookup fails: the requesting institution gets an error, never a default "not an account holder".
- Your error bodies are withheld. When your service answers a non-200 status, Cumbuca passes the status through with a generic
DELEGATED_ERRORenvelope and discards your response body (PII protection). Don't rely on error details propagating; usex-nexus-request-idand your own logs instead. - Error envelope. Gateway errors follow the Open Finance format
{"errors":[{"code","title","detail"}]}and carry the response headersx-nexus-request-idandx-nexus-error-source(client|upstream|gateway).
Error mapping
What each behaviour of your service produces downstream:
| Your service's behaviour | Cumbuca gateway result | What the requesting institution sees |
|---|---|---|
200 with the exact documented shape | 200 data forwarded | 200 with the data |
| Non-200 status | Same status, code DELEGATED_ERROR ("the delegated client returned an error"); your body withheld | 500 INTERNAL_SERVER_ERROR |
| Timeout (> 30 s), connection refused, TLS failure | 502 DELEGATED_UNAVAILABLE ("the delegated client is unavailable") | 500 INTERNAL_SERVER_ERROR |
200 with a non-JSON body or wrong shape | 502 DELEGATED_UNAVAILABLE | 500 INTERNAL_SERVER_ERROR |
200 JSON object without a data array (identifications) | Passes the gateway, rejected downstream | 500 INTERNAL_SERVER_ERROR |
404 on qualifications / financial-relations | 404: recognised as "no record for this document" | 404, same as a non-delegated instance |
What Changes When You Delegate
Registration returns 422
On a delegated instance, both partner-facing registration writes, PUT /personal/identifications/register and PUT /business/identifications/register, are always refused: every authenticated, well-formed request gets 422 and nothing is ever stored (malformed bodies still get the generic 400/415, and missing auth gets 401):
HTTP/1.1 422
x-nexus-error-source: client
x-nexus-request-id: <uuidv7>
{
"errors": [
{
"code": "UNKNOWN",
"title": "Unknown Error",
"detail": "Registration not supported for a delegated client"
}
],
"meta": { "requestDateTime": "2026-07-15T12:00:00Z" }
}
Automatic registration is skipped
The automatic registration that populates Cumbuca's store does not apply when you delegate. After a data-sharing consent reaches AUTHORISED, Cumbuca still attempts the internal write, for personal and business alike, but it is refused with the same 422 (nothing is stored at Cumbuca) and the step is silently skipped. Your service is the only place reciprocity data comes from. Cumbuca also skips adding the identifications permission to consents on a delegated instance, since it never needs to perform that read.
LGPD - nothing stored at Cumbuca
With delegation enabled, no customer identification data for reciprocity is retained at Cumbuca: registration writes are refused before validation, and every read is answered live from your service. Personal data handled for reciprocity stays inside your infrastructure.