# 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. Published 2026-08-11 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/who-controls-this-counterparty --- 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`](/docs/api/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](https://eur-lex.europa.eu/eli/reg/2024/1624/oj) 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](https://curia.europa.eu/site/upload/docs/application/pdf/2022-11/cp220188en.pdf)), 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: ```json { "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`](/docs/api/iban-resolve) → [`/company/lei`](/docs/api/company-lei) → `/company/ownership` and end on a group head it can then screen with [`GET /screen/entity`](/docs/api/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](https://www.gleif.org/en/newsroom/blog/the-lei-in-numbers-active-lei-population-surpasses-3-million-in-q1-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](https://www.gleif.org/en/lei-data/access-and-use-lei-data/level-2-data-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`: ```json { "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: ```json { "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: true` beside `total_children: 141` means the agent always knows whether the list hid something. A truncated list without its count is a silent lie. - **`chain: null` is not `chain: []`.** `null` means "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 `depth` does 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: ```json { "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 `400`s 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 1. **Get a LEI first.** This route takes nothing else. [`GET /company/lei`](/docs/api/company-lei) reaches one from a BIC, an ISIN, a SIREN or an exact name plus country. 2. **Branch on `ultimate_parent.status`, never on `chain.length`.** The HENSOLDT capture is the reason: an empty walk can sit under a named group. 3. **Treat `reporting_exception` as data.** Store the code. `NATURAL_PERSONS` and `NON_PUBLIC` are different facts about your counterparty, and neither is an error. 4. **Screen the parent you found.** Naming a group head is not clearing it; [`GET /screen/entity`](/docs/api/screen-entity) is the check this one sets up. 5. **Keep `as_of` with the answer.** The subsidiary count moved by four in under two weeks. 6. **Discover, then pay.** The `402` → pay → replay cycle is walked with runnable snippets in the [Quickstart](/docs/quickstart), and the machine discovery surfaces are described in [For agents](/docs/for-agents). Every amount and every accepted settlement rail lives in the gateway's [`/catalog`](https://api.invoket.com/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`](/docs/api/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`](/docs/api/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](/docs/api/company-ownership).