Who controls this counterparty? The GLEIF parent hierarchy
One call returns the declared consolidation chain above a LEI — direct parent, ultimate parent, subsidiaries — and why that is not beneficial ownership.
By Matthias Begot · · Updated
An agent screened its counterparty and got a clean result. That tells it about
the counterparty. It says nothing about the group that consolidates it — and a
spotless subsidiary of a designated parent is exactly the case an
endpoint-by-endpoint review walks past.
GET /company/ownership answers the missing
question from a LEI: the direct parent, the ultimate parent, the dated
chain between them, and the direct subsidiaries — served offline from the
GLEIF Level 2 relationship records, published under CC0.
The first thing worth writing down is what the response is not, because the response itself says so. This article walks both: the chain, and its boundary, with captures taken from production on 11 August 2026 against the GLEIF snapshot dated 2026-08-10.
The distinction the response states about itself
GLEIF Level 2 publishes the accounting consolidation parent — the entity
that consolidates this one in its financial statements. That is a different
object from the ultimate beneficial owner under AML rules, and every 200
carries a fixed data.limits sentence saying so:
Relationships are self-declared by the entities to GLEIF and describe accounting consolidation (the parent that consolidates the entity in its financial statements). This is NOT beneficial ownership under AML/KYC rules, and Level 2 publishes no ownership percentage. The absence of a declared parent is not evidence of an absence of control.
Three consequences an integration has to respect:
- No percentages. Level 2 publishes none, so none are served. A UBO determination under Regulation (EU) 2024/1624 turns on a 25%-or-more ownership interest from 10 July 2027 — a number this dataset does not contain and cannot be made to yield.
- No public shortcut exists. Since the Court of Justice struck down unrestricted public access to national beneficial-ownership registers in WM and Sovim SA v Luxembourg Business Registers on 22 November 2022 (CJEU press release 188/22), access runs through a legitimate-interest regime. No pay-per-call API — this one included — is going to hand an agent a UBO.
- Absence is not evidence. “No declared parent” means the register holds no declaration. It does not mean nobody is above.
Selling a consolidation parent as a beneficial owner would be selling one piece of data as another. The endpoint states the distinction instead of letting an integration assume it away.
One call, and the group above
GET /company/ownership?lei=F0HUI1NY1AZMJMD8LP67&depth=5 — a French bank whose
control chain leaves France in one hop:
{
"data": {
"query": { "lei": "F0HUI1NY1AZMJMD8LP67", "direction": "up", "depth": 5 },
"subject": { "legal_name": "HSBC CONTINENTAL EUROPE", "legal_country": "FR",
"entity_status": "ACTIVE", "registration_status": "ISSUED" },
"direct_parent": {
"status": "declared",
"link": { "lei": "MP6I5ZYZBEU3UXPYFY54",
"entity": { "legal_name": "HSBC BANK PLC", "legal_country": "GB" },
"relationship_status": "ACTIVE",
"period": { "start": "2012-06-06", "end": null } },
"exception": null
},
"ultimate_parent": {
"status": "declared",
"link": { "lei": "MLU0ZO3ML4LN2LL2TL39",
"entity": { "legal_name": "HSBC HOLDINGS PLC", "legal_country": "GB" },
"relationship_status": "ACTIVE",
"period": { "start": "2012-06-06", "end": null } },
"exception": null
},
"chain": [
{ "entity": { "legal_name": "HSBC BANK PLC", "legal_country": "GB" }, "period": { "start": "2012-06-06" } },
{ "entity": { "legal_name": "HSBC HOLDINGS PLC", "legal_country": "GB" }, "period": { "start": "2024-06-30" } }
],
"chain_truncated": false,
"cycle_detected": false,
"limits": "…"
},
"provenance": { "source": "gleif", "freshness": { "kind": "snapshot", "as_of": "2026-08-10T00:00:00Z" } }
}
The counterparty is French; the entity that consolidates it is not, and the
jurisdiction that matters for a group-level check moved with it. That is the
whole value of the call: an agent holding an IBAN can go
/iban/resolve →
/company/lei → /company/ownership and end on a
group head it can then screen with
GET /screen/entity.
direction controls the walk — up (default), down, or both — and depth
bounds the upward walk between 1 and 5. A depth outside that range is a
400, never a silent clamp: an agent that asked for 7 has to know it was not
served at 7.
99% of registrants report a parent. About 4% name one.
GLEIF reports that over 3.13 million registrants — 99% of the active LEI population — reported direct and ultimate parent information in Q1 2026 (GLEIF, The LEI in Numbers, 28 April 2026). Read quickly, that sounds like near-total ownership coverage. It is not, and the gap is the single most useful thing to understand before wiring this endpoint.
In the ingested 2026-08-10 snapshot — 3,398,502 LEI records — 139,852 entities declare at least one consolidation relationship, and 3,157,691 carry a reporting exception instead. “Reported parent information” mostly means declared a reason for having no parent to report. Only 53,904 distinct entities are ever named as somebody’s parent.
Those reasons are the payload in the overwhelming majority of calls, so they are served raw, with the GLEIF codes as published. Counts below are direct-parent exceptions in that snapshot; the official definitions are GLEIF’s, from the Reporting Exceptions 2.1 format.
reasons code | What the entity declared | Records |
|---|---|---|
NATURAL_PERSONS | Controlled by natural person(s), no intermediate legal entity | 1,162,019 |
NON_CONSOLIDATING | Controlled by legal entities not subject to consolidation | 1,095,374 |
NO_KNOWN_PERSON | No known person controls the entity — diverse shareholders | 664,473 |
NO_LEI | The parent exists but has no LEI | 123,113 |
NON_PUBLIC | Disclosure barred by law, contract, detriment or missing consent | 108,741 |
| five codes GLEIF deprecated on 1 March 2022 | Still published on old records | 1,996 |
Two readings to resist. First, the codes are self-declared and not
normalized across filers: in this same snapshot BNP Paribas and Société
Générale both sit at the top of their group and both declare
NO_KNOWN_PERSON, while HSBC Holdings plc and Caisse des dépôts et
consignations declare NON_CONSOLIDATING for a materially similar situation.
Branch on status; treat the code as evidence to record, not as a taxonomy to
compute on. Second, NON_PUBLIC is an answer, not a failure — the entity
is telling you it will not say, which is itself a fact worth keeping.
The deprecated codes are passed through rather than folded into NON_PUBLIC:
4,008 rows across 2,171 entities still carry them, and rewriting what a filer
declared would be inventing data.
Three statuses, and an empty chain that is not an absence
direct_parent and ultimate_parent share one shape and exactly three states:
status | Meaning | Populated |
|---|---|---|
declared | A relationship is published | link |
reporting_exception | The entity published a reason for having no parent to report | exception |
not_declared | Neither a relationship nor a published exception | neither |
link and exception are never both populated, and the three are not
interchangeable. Here is the trap, captured live —
GET /company/ownership?lei=21380023ZW5TAIN7YO53:
{
"subject": { "legal_name": "HENSOLDT FRANCE SAS", "legal_country": "FR", "entity_status": "ACTIVE" },
"direct_parent": {
"status": "reporting_exception", "link": null,
"exception": { "category": "DIRECT_ACCOUNTING_CONSOLIDATION_PARENT", "reasons": ["NO_LEI"] }
},
"ultimate_parent": {
"status": "declared",
"link": { "lei": "894500686FYLLZD3M624",
"entity": { "legal_name": "HENSOLDT AG", "legal_country": "DE" },
"relationship_status": "ACTIVE", "period": { "start": "2020-09-21", "end": null } },
"exception": null
},
"chain": [],
"chain_truncated": false
}
chain is empty and the ultimate parent is a German group. The walk cannot
start, because the first link up is missing — the direct parent has no LEI, so
no relationship record exists to follow — but the ultimate parent is served
anyway, since it is declared in its own right rather than derived from the
walk. An agent that reads chain.length === 0 and concludes “no group above
this entity” has just mis-cleared a subsidiary of a European defence group.
Read ultimate_parent.status first. chain is the path, not the verdict.
The third state is real too, and it shows up next to the others. SGS FRANCE SAS (2138001NYOEQGV4ZNG76) declares NO_LEI on its direct parent and is
not_declared at the ultimate level — nothing published, not even a reason.
The subject itself comes back entity_status: "INACTIVE",
registration_status: "RETIRED", served as-is: a diligence signal for the
agent’s own policy, not a verdict the service passes.
Downward: capped, counted, and moving
GET /company/ownership?lei=R0MUWSFPU8MPRO8K5P83&direction=down returns the
entities declaring BNP Paribas as their direct parent — 141 of them in the
2026-08-10 snapshot, of which 100 are served:
{
"subject": { "legal_name": "BNP PARIBAS", "legal_country": "FR" },
"direct_parent": { "status": "reporting_exception", "link": null,
"exception": { "category": "DIRECT_ACCOUNTING_CONSOLIDATION_PARENT",
"reasons": ["NO_KNOWN_PERSON"] } },
"chain": null,
"direct_children": [
{ "lei": "2138002ONAA2E7MTAD20", "entity": { "legal_name": "BNP PARIBAS SYNERGY LIMITED", "legal_country": "GB" },
"relationship_status": "ACTIVE", "period": { "start": "2014-05-15", "end": null } },
{ "lei": "21380036SM8RMK9HM241", "entity": { "legal_name": "CARDIF COLOMBIA SEGUROS GENERALES S A", "legal_country": "CO" },
"relationship_status": "ACTIVE", "period": { "start": "2017-07-11", "end": null } },
{ "lei": "2138003E7UBOEXP61F19", "entity": { "legal_name": "法銀巴黎證券股份有限公司", "legal_country": "TW" },
"relationship_status": "ACTIVE", "period": { "start": "2014-04-22", "end": null } }
],
"children_truncated": true,
"total_children": 141
}
Three properties of that block are contract, not implementation detail.
- The cap is 100 and the total is exact.
children_truncated: truebesidetotal_children: 141means the agent always knows whether the list hid something. A truncated list without its count is a silent lie. chain: nullis notchain: [].nullmeans “you did not ask for this block”;[]means “you asked, and the reference declares nothing”. The service never collapses one into the other, in either direction.- Only direct subsidiaries. There is no multi-level downward org chart, and
depthdoes not apply going down.
The same call documented on the endpoint page returned 137 against the
2026-07-28 image. Four subsidiaries in thirteen days — which is why
provenance.freshness.as_of belongs in whatever the agent stores. Six months
from now, “on what basis did the agent clear this counterparty” is answerable
only if the date was kept with the answer.
Served as published, including the awkward records
Relationship statuses are passed through as GLEIF publishes them, including the
literal string NULL that sits on a handful of old records — six, in this
snapshot. GET /company/ownership?lei=213800TQ8Q2668XD1U88 is one:
{
"subject": { "legal_name": "BIG BEAR CONFECTIONERY LIMITED", "legal_country": "GB",
"entity_status": "INACTIVE", "registration_status": "RETIRED" },
"direct_parent": { "status": "declared",
"link": { "entity": { "legal_name": "RAISIO UK LIMITED", "legal_country": "GB" },
"relationship_status": "NULL",
"period": { "start": "2018-01-01", "end": "2018-12-31" } } },
"ultimate_parent": { "status": "declared",
"link": { "entity": { "legal_name": "Raisio Oyj", "legal_country": "FI" },
"relationship_status": "ACTIVE",
"period": { "start": "2018-01-01", "end": "2018-12-31" } } }
}
A closed period.end, one link marked NULL and one marked ACTIVE on the
same record. Normalizing that into a tidy "ACTIVE" would make the response
prettier and wrong. Where several relationships do compete, the selection is
deterministic and stated: a parent in force beats a historical one — an
ACTIVE link wins over an INACTIVE one, then the most recent period, then
the LEI. The order never depends on what the storage engine happens to return,
because serving a stale link would name a wrong parent.
Two more guards behave the same way. A declared parent whose own Level 1 record
is absent is served with its LEI and entity: null — the link is published,
the identity record is not, and neither is hidden nor invented. And the upward
walk keeps the set of visited LEIs: an already-seen LEI stops it and raises
cycle_detected: true. Real consolidation data has no cycles, but nothing in
the format forbids one, and an infinite loop on a valid input would be a 5xx or
worse.
Billing and failure modes
Everything the service can answer is a billed 200, including every form of
“the register declares nothing here”. Everything it cannot answer leaves the
200 range and is not billed.
| Case | Result |
|---|---|
| Chain found, up, down or both | 200, billed |
reporting_exception — a reason instead of a parent | 200, billed |
not_declared, empty chain, empty direct_children | 200, billed |
lei absent | 400 MISSING_PARAMETER, not billed |
depth outside 1..5, unknown direction, malformed lei | 400 INVALID_INPUT, not billed |
| Well-formed LEI failing its ISO 17442 check digits | 400 INVALID_CHECKSUM, not billed |
| Well-formed LEI unknown to the reference | 404 NOT_FOUND, not billed |
| GLEIF dataset not ingested | 503 DATA_UNAVAILABLE, not settled |
The two 400s that look alike are deliberately distinct, because they demand
opposite reactions — fix the input, versus conclude the entity is not in the
register:
GET /company/ownership?lei=R0MUWSFPU8MPRO8K5P84
→ 400 { "code": "INVALID_CHECKSUM", "error": "lei failed its ISO 17442 check digits: likely a typo" }
GET /company/ownership?lei=R0MUWSFPU8MPRO8K5P83&depth=7
→ 400 { "code": "INVALID_INPUT", "error": "depth must be between 1 and 5" }
A missed refresh keeps serving the last good snapshot under its honest
as_of — staleness is declared in provenance.freshness, never turned into a
5xx.
Wiring it up
- Get a LEI first. This route takes nothing else.
GET /company/leireaches one from a BIC, an ISIN, a SIREN or an exact name plus country. - Branch on
ultimate_parent.status, never onchain.length. The HENSOLDT capture is the reason: an empty walk can sit under a named group. - Treat
reporting_exceptionas data. Store the code.NATURAL_PERSONSandNON_PUBLICare different facts about your counterparty, and neither is an error. - Screen the parent you found. Naming a group head is not clearing it;
GET /screen/entityis the check this one sets up. - Keep
as_ofwith the answer. The subsidiary count moved by four in under two weeks. - Discover, then pay. The
402→ pay → replay cycle is walked with runnable snippets in the Quickstart, and the machine discovery surfaces are described in For agents.
Every amount and every accepted settlement rail lives in the gateway’s
/catalog, which is the single source of
truth — no price appears in this article. If you would rather not
orchestrate steps 1, 2 and 4 yourself,
GET /preflight/supplier runs identity,
insolvency, VAT, reachability, the control chain and the screening of the
ultimate parent in one settlement and returns a single verdict; and
GET /company/report bundles the French
counterparty picture by SIREN, ultimate parent included.
What none of them will do is name a beneficial owner. That answer is not in
this dataset, it is not in any public one since 2022, and an endpoint that
claimed otherwise would be worth less than one that says so. The full parameter
and field contract is on the
GET /company/ownership documentation.