GLEIF - Global Legal Entity Identifier Foundation (FSB-chartered)
global LEI registry - ~3.3M legal entities + ~647K parent/child relationships
Source
Feed (what to expect)
GLEIF daily full files (no auth); live-fetched - the LEI identity backbone.
Quirks
Reconciliation
Schema
Feeds (axes)
Record kinds
Identifier schemes
Phase 2 desk landscape, written 2026-05-10. Reference document for the GLEIF fetcher built against the v1 schema + ADR 010 lineage substrate + ADR 011 cross-source entity reconciliation. Cross-references:
docs/adr/010-lineage-records-share-canonical-pipeline.md,docs/adr/011-cross-source-entity-reconciliation.md,docs/LINEAGE.md,docs/DATASOURCES.md,docs/phases/02-multi-jurisdictional-and-lineage.md.
Live-data observations (verified 2026-05-10 against the GLEIF Concatenated File catalogue at
leidata.gleif.org/api/v1/and the JSON-API atapi.gleif.org/api/v1/). All counts and URLs below were API-verified live.
The canonical bulk URLs are stable. GLEIF publishes daily full files (no deltas) at:
- LEI Level 1 (LEI-CDF):
https://leidata.gleif.org/api/v1/concatenated-files/lei2/get/<id>/zip
the
<id>rolls daily; the latest is queryable viahttps://leidata.gleif.org/api/v1/concatenated-files/lei2/?page=1which returns the most recent first. Verified 2026-05-09: id=41048, content_date=2026-05-09, 3,304,415 records, ~490 MB compressed XML.
- Reporting Relationships (RR-CDF):
https://leidata.gleif.org/api/v1/concatenated-files/rr/get/<id>/zipsame daily pattern. Verified 2026-05-09: id=41051, 647,421 records, ~35 MB compressed XML.
- Reporting Exceptions:
https://leidata.gleif.org/api/v1/concatenated-files/repex/get/<id>/zipid=41054, 5,929,723 records, ~45 MB. Records that aren't parent edges - entities that explicitly except from reporting consolidation (state-owned, natural-person owners, legal restrictions). Useful for confidence labelling but not for direct edge emission. A.7 ingests LEI-CDF + RR-CDF; Reporting Exceptions deferred.
Vintage =
content_datefrom the concatenated-file metadata. The catalogue API returns{"id": ..., "type": "lei2", "content_date": "2026-05-09 09:00:01", "record_count": ...}. Fetcher should bumpvintageto the latest content_date on each--forcerun.LEI-CDF schema (verified via
api.gleif.org/api/v1/lei-records/<lei>). Each record carries:
Field Example (Glencore PLC, LEI 2138002658CPO9NBH955)lei2138002658CPO9NBH955entity.legalName.nameGLENCORE PLCentity.legalName.languageenentity.otherNames[].name(previous legal names - useful for alias matching) entity.legalAddress.countryJE(Jersey)entity.legalAddress.city/regionSt Helier/JE-3entity.headquartersAddress.country(often differs from legalAddress) entity.jurisdictionJE(ISO-3166 alpha-2; same as legalAddress)entity.categoryGENERAL/FUND/BRANCH/RESIDENT_GOVERNMENT_ENTITYentity.legalForm.idELF code (4-char alphanumeric, ISO 20275) entity.registeredAt.idBusiness-registry ID ( RA000787for Chile)entity.registeredAsNational-registry ID ( 61704000-Kfor Codelco)registration.statusISSUED/LAPSED/RETIRED/MERGEDRR-CDF (Level 2) schema carries directional consolidation edges. Verified via
api.gleif.org/api/v1/lei-records/<lei>/direct-parent-relationship:
Field Example (Glencore UK Ltd → Glencore International AG) relationship.startNode.id213800GOKGBQK1IP1D30(child / subsidiary)relationship.endNode.id213800PSSU2QXF1WLR89(parent)relationship.typeIS_DIRECTLY_CONSOLIDATED_BY/IS_ULTIMATELY_CONSOLIDATED_BYrelationship.statusACTIVE/ inactiverelationship.periods[]accounting + relationship periods with start/end validFrom/validToTop-level relationship validity registration.corroborationLevelFULLY_CORROBORATED/PARTIALLY_CORROBORATEDregistration.corroborationDocumentsACCOUNTS_FILING/REGULATORY_FILING/ etc.registration.corroborationReferenceURL to the supporting document (annual report, etc.) Direction convention is FF-aligned:
startNode= child / subsidiary,endNode= parent. No swap needed at emit (unlike CorpWatch where source/target are flipped).The two relationship types are duplicative for our purposes. Every entity reports up to two edges: one DIRECT_CONSOLIDATED_BY (immediate accounting parent) and one ULTIMATELY_CONSOLIDATED_BY (top-of-chain). The ultimate edge is derivable by traversing direct edges, so A.7 emits only
IS_DIRECTLY_CONSOLIDATED_BYasRelationship(kind="consolidates"). The ultimate edge is redundant and skipped at emit time; downstreamwalk_ownershiptraversal recovers it from the direct edges.Coverage rate. ~647K relationship records vs ~3.3M LEI records = ~20 % of LEIs have a disclosed direct parent. Reporting Exceptions account for most of the remainder (no consolidating parent, natural-person ownership, legal restrictions). Coverage skews toward listed companies (10-K filers, EU MAR-regulated, regulatory-reporting institutions).
Cross-source overlap with Wikidata operator entities (the primary B.1.4 win). Wikidata's P1278 (LEI) is populated on 819 of the 4,989 operator entities emitted in B.1.4 (16.4 %). Each of those 819 LEIs gets an exact match against GLEIF L1 - same LEI string, no fuzzy matching needed. The A.6
same_entity_asmatcher will bridge these by name + jurisdiction; an LEI-anchored match is HIGH confidence. Verified spot-check: Glencore PLC LEI2138002658CPO9NBH955(in our copper dossier
- Wikidata operator set) → GLEIF resolves to "GLENCORE PLC", jurisdiction JE.
This is the reference for the v1 GLEIF fetcher: lineage-side
only (no facility-side records emitted), same shape as CorpWatch.
Per ADR 010, each LEI-CDF record → one Entity (HIGH confidence -
LEI is a strong universal anchor); each RR-CDF
IS_DIRECTLY_CONSOLIDATED_BY record → one ownership_relationship
with kind="consolidates". The fetcher writes exclusively to the
resolved tier's entity/source_id=gleif/ and
ownership_relationship/source_id=gleif/ partitions.
Publisher. GLEIF - the Global Legal
Entity Identifier Foundation, a not-for-profit chartered by the
Financial Stability Board (G20) in 2014. GLEIF accredits ~40 Local
Operating Units (LOUs) globally, each issuing LEIs for their
jurisdiction; GLEIF then concatenates daily into the global file
served at leidata.gleif.org.
License posture. All LEI data is public-domain - no
registration fees, no API tokens, no commercial-use restrictions.
GLEIF's own framing: "a unique and free data source" for tracking
ownership structures. FF includes
Provenance.fetched(source_id="gleif", source_url=<concat-file URL>, ...)
on every emitted record. The corroboration document URLs in RR-CDF
records are NOT redistributed (they're external links to operator
SEC filings, EU regulatory filings, etc.) - Provenance points at the
GLEIF concat file, not the underlying primary disclosure.
Coverage. Universal in scope, regulatory in coverage:
registration.status becomes LAPSED if
renewal lapses; ISSUED is the active state). FF admission filters
to status in {ISSUED, LAPSED} - RETIRED and MERGED are
end-of-life states tracked via lifecycle.Granularity. Entity-level, not facility-level. GLEIF's coverage is the legal-entity layer that sits behind facility operators - exactly the layer FF's lineage submodule needs to give operator dossier rows resolved canonical names.
Cadence. Daily full files. The fetcher's vintage field
captures the file's content_date from the catalogue metadata; a
re-fetch with a fresh content_date is a new vintage.
Access. Free, no auth, three surfaces:
leidata.gleif.org/api/v1/concatenated-files/{type}/get/<id>/ziplei2 (LEI-CDF Level 1), rr
(RR-CDF Level 2), repex (reporting exceptions).api.gleif.org/api/v1/lei-records/<lei> - the
per-LEI surface used to verify the schema during landscape
research. Not used by the fetcher - bulk-files are cheaper at
our scale.gleif.org/en/lei-data/gleif-concatenated-file/download-the-concatenated-fileFormat. XML, conforming to the Common Data File standard
(LEI-CDF 3.1 for Level 1; RR-CDF 2.1 for Level 2). Schema published
at gleif.org/en/about-lei/code-lists/. Both files are zipped XML
inside the downloaded .zip.
The two files FF ingests:
| File | Type | Records | Compressed | Uncompressed (est.) | XML root |
|---|---|---|---|---|---|
| LEI Level 1 | XML | 3,304,415 | ~490 MB | ~3.5 GB | LEIData containing LEIRecords |
| RR-CDF (Level 2) | XML | 647,421 | ~35 MB | ~250 MB | RelationshipData containing RelationshipRecords |
Why XML, not CSV. GLEIF publishes CSV exports as a convenience
but the canonical format is XML - the relationship records' nested
periods[] and corroboration metadata don't flatten cleanly to CSV
columns. Both files are well-formed XML; FF's parser uses
xml.etree.ElementTree.iterparse for streaming over multi-GB
documents without materialising the full tree.
The download story. ~525 MB combined for a full re-fetch.
Mirrored mirrors don't matter much at this size; download from
leidata.gleif.org directly with the file-cache step
(download_stream, BULK_FILE cache strategy). The fetcher
supports a --bulk-source <path> flag (matching CorpWatch's pattern)
that points at pre-extracted XML files for dev iteration.
Vintage anchor. The catalogue API at
leidata.gleif.org/api/v1/concatenated-files/lei2/?page=1 returns
the content_date for each daily file. The fetcher's
fetch() method:
content_datedata/raw/gleif/<content_date>/lei-cdf.xml.zip and
data/raw/gleif/<content_date>/rr-cdf.xml.zipEntityPer ADR 010 + ADR 011, each LEI-CDF record admits to one
:class:Entity (lineage submodule). Field mapping:
| FF Field | LEI-CDF Source |
|---|---|
entity_id | "gleif_lei:<LEI>" (uses SCHEME_GLEIF_LEI) |
canonical_name | entity.legalName.name |
jurisdiction | entity.jurisdiction (ISO-3166 alpha-2) |
lei | <LEI> (same as anchor for cross-source match) |
entity_type | Derived from entity.category + entity.legalForm.id (ELF code) |
confidence | HIGH - LEI is a strong universal anchor |
provenance | Provenance.fetched(source_id="gleif", source_url="<concat-file URL>", source_hash=<sha256>) |
Admission filter: registration.status in {ISSUED, LAPSED}.
Skip RETIRED (entity dissolved) and MERGED (consolidated into
another LEI; the merge-target entity carries the canonical name now).
LAPSED entities are admitted because they may still appear as
parents/children in active relationships; the lapse is a
data-quality flag, not a "doesn't exist" signal.
entity_type derivation. ISO 20275 ELF codes map to FF's
five-class taxonomy (corporation / partnership / state_vehicle
/ trust / natural_person / other). A lookup table covers the
~200 most common ELF codes (LLC, GmbH, SA, plc, AG, Sàrl, etc.); the
fallback is corporation for entity.category=GENERAL,
state_vehicle for RESIDENT_GOVERNMENT_ENTITY, other for
BRANCH / FUND.
ownership_relationshipPer ADR 010, each IS_DIRECTLY_CONSOLIDATED_BY record admits to
one :class:Relationship. IS_ULTIMATELY_CONSOLIDATED_BY records
are skipped at emit time - they're derivable via traversal of
direct edges (per ADR 010's walk_ownership design) and emitting
both kinds would double-count parent edges.
Field mapping:
| FF Field | RR-CDF Source |
|---|---|
from_entity | "gleif_lei:" + relationship.startNode.id |
to_entity | "gleif_lei:" + relationship.endNode.id |
kind | "consolidates" (FF's accounting-line kind) |
valid_from | relationship.periods[].startDate where type="RELATIONSHIP_PERIOD" (or validFrom) |
valid_to | Same source, endDate |
stake_percent | null (GLEIF L2 doesn't carry per-edge percentages - that's L3 / private datasets) |
confidence | HIGH when corroborationLevel="FULLY_CORROBORATED"; MEDIUM for PARTIALLY_CORROBORATED |
provenance | Provenance.fetched(source_id="gleif", ...) |
Direction convention: GLEIF's startNode = child / subsidiary,
endNode = parent. FF's from_entity = subsidiary, to_entity =
parent. The directions ALIGN - no swap needed at emit. (Unlike
CorpWatch where source/target are flipped.) Lock with a fixture
test to prevent regression.
Facility records. GLEIF is entity-only.IS_ULTIMATELY_CONSOLIDATED_BY edges. Derivable; would
double-count.LEI status semantics. ISSUED is active-and-current.
LAPSED is "registration didn't renew" - entity may still exist,
but GLEIF stopped vouching for the metadata. RETIRED is
"entity dissolved" (use lifecycle event downstream). MERGED
means the entity was consolidated into another LEI - the merger
target's record carries the canonical name now. FF admits
ISSUED + LAPSED for entity-side breadth; RETIRED + MERGED
are out.
Jurisdiction can disagree between legalAddress.country and
jurisdiction. Rare but real: a Cayman-incorporated entity
can have its legalAddress in NY (registered office) while
jurisdiction=KY. FF uses entity.jurisdiction - the formal
incorporation jurisdiction is what matters for lineage
traversal.
legalName.language is sometimes wrong. The LEI registry
tries to record the language of the legal name, but ~5 % of
records carry a language code that doesn't match the actual
string (e.g., a French-language name tagged en). For canonical
name selection, FF takes the legal name as-is (don't try to
re-detect language).
otherNames[] carries previous legal names. Useful for
alias matching at the synthesis layer when entities rebrand
(Norilsk Nickel → MMC Norilsk Nickel; KGHM Polska Miedz S.A.
→ same name historically). A.7 doesn't emit alias
relationships at fetch time - that's synthesis-layer work.
corroborationLevel matters. FULLY_CORROBORATED means GLEIF
verified the relationship against an accounts filing or
regulatory disclosure. PARTIALLY_CORROBORATED is operator
self-reported without external corroboration. FF maps
accordingly: HIGH for fully, MEDIUM for partially.
Daily files are FULL - no deltas. Each daily file is the
complete current state. Re-fetching with --force always
downloads the full file. Streaming the XML keeps memory bounded.
The XML is well-formed but verbose. A single LEIRecord
element is ~80 lines of nested XML. Sax-style streaming
(iterparse) keeps memory under 100 MB during ingest.
GLEIF's payoff for FF is the LEI-anchored entity backbone that makes operator-side resolution global rather than US-only:
Foreign private issuers. SEC EDGAR (via CorpWatch) covers US
10-K filers; GLEIF covers global financial-market participants.
BHP Group Limited (LEI WZE1WSENV6JSZFK0JC28), Glencore plc
(2138002658CPO9NBH955), Antofagasta plc
(213800MY6QVH4FVLD628), Rio Tinto plc
(213800YOEO5OQ72G2R82), Rio Tinto Limited
(529900X2VMAQT2PE0V24) - all in GLEIF, none in CorpWatch.
Wikidata operator alignment. B.1.4 emitted 819 Wikidata
operator Entity records carrying P1278 LEIs. Each of these is
an exact-string match against a GLEIF LEI. The A.6
same_entity_as matcher will bridge them as HIGH-confidence
alias edges (LEI is a universal anchor; matched LEIs are the
same legal entity by definition).
Apex-parent traversal globalised. Today's walk_ownership
traversal stops at CorpWatch apex parents (US-listed). With
GLEIF L2's 647K direct-parent edges in the graph, traversals
can continue past US boundaries - Glencore UK Ltd → Glencore
International AG → Glencore plc traversal works end-to-end
inside GLEIF, no need to cross sources.
Sovereign-state ultimate parents. GLEIF's
entity.category=RESIDENT_GOVERNMENT_ENTITY flags state-owned
enterprises. FF's entity_type="state_vehicle" carries this
forward; the apex-state pattern (Codelco → Republic of Chile;
MIND ID → Republic of Indonesia) becomes queryable at the
entity-type axis.
Mining-sector LEI density. Rough estimate from cross-checks: ~70 % of major mining operators (top-10-by-output per commodity) have an LEI. Concentration is highest in EU / Australia / UK / US listed parents; lowest in Chinese SOEs (some have LEIs, many don't), Russian operators (sanctions complications), and DRC/Zambia private holding chains.
Implementation maps to four sub-steps under the A.7 banner, mirroring A.5:
--bulk-source flag (half-day).
Stream the catalogue API, resolve the latest L1 + L2 file IDs,
download both via download_stream to
data/raw/gleif/<content_date>/. Skip-the-download dev path so
~525 MB doesn't re-fetch every iteration.Entity emission (one day).
Stream the LEI-CDF XML via iterparse; emit one Entity per
admitted LEI. ELF code → entity_type lookup table. Admission
filter on registration.status.ownership_relationship emission
(half-day).
Stream the RR-CDF XML; emit one Relationship per
IS_DIRECTLY_CONSOLIDATED_BY record with kind="consolidates",
confidence mapped from corroborationLevel. Skip
IS_ULTIMATELY_CONSOLIDATED_BY (derivable).The ~525 MB combined download dominates the ingest time; per- record parse is fast (XML iterparse against the 3.3M-row LEI-CDF should land in ~5 minutes). Total A.7 cycle: 2-3 days.