From an IBAN to the legal entity behind it
An agent holding a bank account, a security or a SIREN can name the company behind it in two calls — GLEIF identity by LEI, BIC, ISIN, SIREN or exact name plus country.
By Matthias Begot ·
An agent that is about to pay an invoice holds an IBAN. An agent reconciling a
portfolio holds an ISIN. An agent onboarding a French supplier holds a SIREN.
None of those is a company — they are handles that point at one.
GET /company/lei turns any of them into an official
legal identity: legal name, jurisdiction, legal form, entity and registration
status, addresses, national registration number, and the market identifiers
mapped to the entity. It answers on a German, Irish, Colombian or Japanese
counterparty as readily as a French one, served offline from the GLEIF LEI
reference — the public, CC0 global identity register.
The chain worth wiring is two calls long: IBAN → BIC → legal entity. Below it is walked with responses captured from production on 11 August 2026, against the GLEIF snapshot dated 2026-08-10 — including the case where the chain doesn’t resolve, which is the part most integrations get wrong.
The two-call chain, captured
Call 1 — the bank behind the account.
GET /iban/resolve validates the IBAN and returns the
institution:
{
"data": {
"iban": "FR7630004000031234567890143",
"valid": true, "country": "FR", "bank_code": "30004",
"bank": { "name": "BNP PARIBAS", "bic": "BNPAFRPP" },
"reachable": true,
"sepa_reachability": { "sct": true, "sct_inst": true, "sdd_core": true, "sdd_b2b": true, "match": "exact" }
},
"provenance": { "source": "national-bank-registries+epc-sepa-registers", "freshness": { "kind": "snapshot", "as_of": "2026-06-11T00:00:00Z" } }
}
Call 2 — the legal entity behind the bank. GET /company/lei?bic=BNPAFRPP,
abridged:
{
"data": {
"query": { "key": "bic", "value": "BNPAFRPP" },
"entity": {
"lei": "R0MUWSFPU8MPRO8K5P83",
"legal_name": "BNP PARIBAS",
"other_names": [{ "name": "HELLO BANK!", "type": "TRADING_OR_OPERATING_NAME" }],
"legal_form": "K65D",
"legal_jurisdiction": "FR",
"entity_status": "ACTIVE",
"entity_category": "GENERAL",
"registration": {
"status": "ISSUED",
"initial_registration_date": "2012-12-31",
"last_update_date": "2026-01-26",
"next_renewal_date": "2027-02-07",
"managing_lou": "969500Q2MA9VBQ8BG884"
},
"legal_address": { "country": "FR", "city": "PARIS", "postal_code": "75009", "line": "16 BOULEVARD DES ITALIENS" },
"registration_authority": {
"ra_code": "RA000189", "registration_number": "662042449",
"siren": "662042449", "verified_against_sirene": true
},
"identifiers": { "bics": ["BNPAAEADXXX", "…"], "bics_total": 76, "isins": ["CH0419041642", "…"], "isins_total": 41631 }
},
"matches": [], "total_matches": 1
},
"provenance": {
"source": "gleif",
"fetched_at": "2026-08-11T06:17:27Z",
"freshness": { "kind": "snapshot", "as_of": "2026-08-10T00:00:00Z" }
}
}
Three details in that response are worth reading closely.
- The BIC-8 resolved. GLEIF only publishes BIC-11; an agent that took
BNPAFRPPoff an IBAN never has the branch suffix. The lookup queries both forms, soBNPAFRPPfinds the entity through the mappedBNPAFRPPXXX. other_namescarries the trading name. An invoice signed “Hello bank!” and a register entry reading “BNP PARIBAS” are the same counterparty, and the response says so instead of leaving the agent to guess.- The lists are capped, the totals are not.
bicsandisinsare capped at 20 entries, butbics_total: 76andisins_total: 41631are exact. The agent always knows whether the cap hid something — a truncated list without its count is a silent lie.
From here the third hop is GET /company/ownership:
who consolidates this entity. Bank account → institution → group, in three
settlements, with no key and no account anywhere in the chain.
Five keys, exactly one per call
| Key | What an agent typically holds it from | Validation |
|---|---|---|
lei | A counterparty record, a regulatory filing | ISO 17442 check digits |
bic | An IBAN (via /iban/resolve), a payment message | Form only — ISO 9362 has no check digit |
isin | A security, a portfolio line | Luhn check digit |
siren | A French invoice, a supplier form | Luhn check digit |
name + country | A contract header, an email footer | Exact match after normalization |
Zero keys or two keys is a 400 INVALID_INPUT — the service does not rank keys
or pick one. A well-formed key that fails its check digit gets a distinct
400 INVALID_CHECKSUM rather than a 404, because those two answers demand
opposite reactions: fix your input, versus conclude the entity is not in the
register.
GET /company/lei?lei=R0MUWSFPU8MPRO8K5P84
→ 400 { "code": "INVALID_CHECKSUM", "error": "lei failed its ISO 17442 check digits: likely a typo" }
The siren bridge is worth one line of explanation, because it is the one key
GLEIF does not publish directly. GLEIF carries a national registration
authority and a self-declared, messy registration number per entity; the bridge
is derived at ingest under three closed rules — only the two French
authorities whose number is a SIREN are bridged, separators are stripped, and
a 14-digit SIRET yields its 9-digit prefix. A number that merely looks like a
SIREN under another authority is not bridged: a Luhn coincidence is not a
declaration. Where the derived SIREN could be cross-checked against the served
Sirene register, verified_against_sirene says so — and it has three states,
true, false and null, because “GLEIF declares a SIREN that does not exist”
and “we had no register to check against” are different facts.
When the chain breaks: the BIC map is self-declared
This is the failure an agent must handle, and it is not exotic. Société Générale’s head-office BIC returns nothing:
GET /company/lei?bic=SOGEFRPP → 404 NOT_FOUND, not billed
The entity is plainly in the register. Query it by name instead:
GET /company/lei?name=Société%20Générale&country=FR
→ 200 { "entity": { "lei": "O2RNE8IBXP4R0TD8PU41", "legal_name": "SOCIETE GENERALE",
"legal_jurisdiction": "FR", "entity_status": "ACTIVE",
"registration_authority": { "siren": "552120222", "verified_against_sirene": true },
"identifiers": { "bics": ["NEWGFRP1XXX"], "bics_total": 1 } } }
One declared BIC, and it is not the one on the IBAN. The GLEIF BIC mapping
reflects what each entity chose to declare, not what SWIFT assigns. So the
IBAN → BIC → LEI chain is a high-value shortcut, not a guarantee: BNP Paribas,
Crédit Agricole, BRED, Crédit Mutuel and Qonto all resolve from their BIC-8 in
the same snapshot; Société Générale and Caisse d’Épargne do not. Wire the
fallback — bic first, then name + country, or the siren you already hold
from the invoice.
That a 404 here is not billed matters for exactly this reason: an agent
that retries through a second key has paid once, for the call that answered.
An ambiguous key returns the ambiguity, never a pick
Several entities can share a normalized name inside one country. The service
then serves entity: null, the candidates, and the exact count:
{
"query": { "key": "name", "value": "victoire", "country": "FR" },
"entity": null,
"matches": [
{ "lei": "9695003FOBCTRX2HMC63", "legal_name": "VICTOIRE", "entity_status": "ACTIVE", "registration_status": "ISSUED" },
{ "lei": "969500M534WKMOFRE728", "legal_name": "VICTOIRE", "entity_status": "ACTIVE", "registration_status": "LAPSED" },
"… 4 more …"
],
"total_matches": 6
}
Six VICTOIRE in France. matches is capped at 10 and total_matches is
exact. An agent handed one arbitrarily chosen entity has been given a fact it
cannot audit; an agent handed six knows its key is too weak and can escalate to
a SIREN or a LEI. Note also query.value: "victoire" — the echoed normalized
key, which is why Société Générale matched societe generale above. The
matching is exact after normalization and never fuzzy: “close enough” would
be a guess wearing an official register’s authority.
LAPSED is a fact, and it is a common one
entity_status and registration.status are served exactly as published, from
closed sets, with no editorial comment:
| Field | Closed set |
|---|---|
entity_status | ACTIVE · INACTIVE · the literal NULL GLEIF publishes on some records |
registration.status | ISSUED · LAPSED · RETIRED · DUPLICATE · ANNULLED · PENDING_TRANSFER · PENDING_ARCHIVAL |
Two of those deserve care. A LAPSED registration means the entity did not
renew its LEI on the anniversary — it does not mean the company is dead. It
is common: GLEIF reported the overall renewal rate stable at 56.6% in
Q1 2026, on an active population that passed 3.02 million entities
(GLEIF, The LEI in Numbers, 28 April 2026).
In the VICTOIRE list above, three of six ACTIVE entities carry a LAPSED
registration.
An entity found with a lapsed registration is therefore a 200, and it is
billed — that status is the diligence signal the agent came for, delivered
raw for the agent’s own policy to interpret. There is no risk score in this
response, and there will not be one.
Why a payment agent is meeting the LEI more often
The LEI stopped being a securities-only identifier. CPMI’s harmonised ISO 20022 data requirements for cross-border payments — updated report published 26 February 2026, with adoption flexibility maintained until the end of 2027 (BIS/CPMI) — propose the LEI and/or the BIC to identify financial institutions in payment messages (GLEIF press release). An agent that can move between a BIC and an LEI is moving between the two identifiers the payments stack is converging on.
What it will not do
- No beneficial ownership. The LEI reference publishes an accounting
consolidation parent, never the ultimate beneficial owner under AML rules.
That distinction is spelled out on
GET /company/ownership. - No fuzzy search, no search without a country, no search by address.
- No financials, no scoring, no invented labels.
legal_formis served as the raw ELF code (K65D,SGST) because GLEIF publishes no label for it. - No sanctions verdict. Naming an entity is not screening it — that is
GET /screen/entity. - Register coverage, not world coverage. An entity has a LEI only because it
obtained one, typically to trade on markets or to report under a regulation.
A small French company has no reason to appear here, and its absence says
nothing about it:
GET /company/resolveis the French identity card for that case.
Billing and failure modes
| Case | Result |
|---|---|
Entity found — including LAPSED, INACTIVE, RETIRED | 200, billed |
Key ambiguous — entity: null plus candidates and exact count | 200, billed |
Zero or two keys, name without country, malformed key | 400 INVALID_INPUT, not billed |
| Well-formed key failing its check digits | 400 INVALID_CHECKSUM, not billed |
| Well-formed key unknown to the reference | 404 NOT_FOUND, not billed |
GLEIF dataset not ingested, or siren= on an image predating the bridge | 503 DATA_UNAVAILABLE, not settled |
That last 503 is deliberate rather than a 404: an image with no bridge table
cannot assert “this SIREN has no LEI”, so it declares an unavailability and the
four other keys keep working. And a missed refresh keeps serving the last good
snapshot under its honest as_of — staleness is never turned into a 5xx.
Unlike GET /company/peppol, there is no
200 not_registered equivalent here. Peppol publishes a directory, so “absent
from it” is a fact worth paying for. GLEIF publishes a register of entities that
applied; the absence of a LEI is not a published fact, it is the absence of a
record — hence an unbilled 404.
Wiring it up
- Pick one key, and rank your fallbacks.
lei>siren/isin>bic>name+country. Expect thebichop to miss on some institutions and fall through, unbilled. - Branch on
entityvsmatches, not on the array length.entity: nullwithtotal_matches > 1means “narrow your key”, which is a different instruction from “not found”. - Store
freshness.as_ofwith the answer. The response is a snapshot of the GLEIF Golden Copy, and six months from now “on what basis did the agent pay this counterparty” is answerable only if the date was kept. - Read status as input, never as verdict.
LAPSEDandINACTIVEfeed your policy; the service passes no judgement. - 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 buy the whole
French counterparty picture in one settlement instead of orchestrating the
family yourself, GET /company/report bundles
identity, insolvency announcements, VAT, Peppol reachability, LEI and ownership
by SIREN.
The captures above are snapshot-dated illustrations, not a contract: a
GLEIF refresh moves a renewal date, an entity declares a new BIC, a name becomes
ambiguous. Only the rules are guaranteed — one key per call, an ambiguity served
as an ambiguity, status served as published, totals exact beside capped lists,
and an unanswerable request never billed. The full parameter and field contract
is on the GET /company/lei documentation.