GET /preflight/supplier
Runs every check on a counterparty, in one call, before your agent onboards it, pays it or invoices it:
| Block | Question it answers | Source |
|---|---|---|
identity | Does it exist, and is it still active? | INSEE Sirene, or GLEIF |
events | Insolvency, deregistration, sales — what has been gazetted? | BODACC |
vat | Is its EU VAT number valid right now? | VIES, live (or our last firm verdict, dated) |
peppol | Can it receive an e-invoice? | Peppol directory |
group | Who declares control over it? | GLEIF Level 2 |
name_screening | Is the company itself designated? | OFAC SDN & Consolidated, EU FSF, UN |
parent_screening | Is its ultimate parent designated? | the same lists |
bank | Is its IBAN payable? (only when you pass iban) | /preflight/payment |
It returns one verdict — ok, review, stop or insufficient_coverage —
with every reason attached to the block it came from, and each block’s full
answer, in the exact shape of the matching standalone endpoint, next to it.
x402 golden rule: the agent pays for the answer to its question. A
counterparty in liquidation is a useful answer → 200 with a motivated
stop. A well-formed identifier that no register knows is a 404, unbilled:
no empty pre-flight is ever billed.
The rule that makes the verdict worth reading
okis impossible while any block isunavailable. A check that did not happen never passes.
A French company with no LEI has no publishable control chain — so
parent_screening cannot run either, and the answer is
insufficient_coverage, never ok. That is a structural limit of the LEI
reference, stated as such, not an outage and not a silence.
Two distinctions do all the work here, and the composite never blurs them:
not_applicableis an answer;unavailableis a gap. “GLEIF publishes that there is no parent above this entity” is an answer. “We could not establish the control chain” is a gap — and only the gap forbidsok.- A gap is not a signal. A VAT number that VIES could not check is a gap; a VAT number the member state firmly answers as invalid is a signal, and it weighs on the verdict. A firm verdict we already held and served from cache is a verdict too, so it is not a gap - it is dated, and it says so.
The verdict is always exactly the maximum severity among the reasons, and every reason carries its block, a stable code, what it weighed and a detail when there is one — so you can recompute the verdict from the blocks instead of taking it on trust.
One call in place of several
This endpoint is not priced at the catalog’s flat per-call rate, and that is
deliberate: one call runs seven checks — eight with an IBAN. It performs the
work of the /company/* and /screen/entity calls, in one round-trip and
one x402 settlement, and adds what no sequence of unit calls gives you:
the screening of the ultimate parent, which an endpoint-by-endpoint review
misses, and the decision table that turns eight independent answers into one
verdict with an explicit rule for what happens when a check could not run.
No price is written on this page. The gateway’s
/catalog is the single source of truth for every amount,
including the unit endpoints, so you can compare them there.
Scope and limits
Every response carries this sentence in data.limits, verbatim:
This verdict is a deterministic decision table over sourced, dated facts from public registers; it is not a credit opinion, a risk score or a recommendation to act. ‘ok’ only means that every requested check was answered and none of them raised a signal, on the data served (see each block’s provenance and coverage). Parent screening covers the declared accounting consolidation parent, never the beneficial owner.
In particular:
- Control chain ≠ beneficial ownership. GLEIF Level 2 publishes the accounting consolidation parent, self-declared, with no percentage. It is not a UBO under AML/KYC rules, and the absence of a declared parent is not evidence of an absence of control.
- No search by name. The entry key is a SIREN or an LEI.
- No credit score, no financial statements, no directors.
- A VAT verdict may be dated. When the member state does not answer, the
vatblock serves our last firm verdict on that number, up to 24 hours old, with its age attached — see below.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siren | string | one of | 9-digit SIREN, Luhn-checked |
lei | string | one of | 20-character LEI (ISO 17442 check digits) |
iban | string | no | Adds the bank block. Absent means the block is absent — not an empty block |
vat_number | string | no | VAT number of any member state; absent, it is derived from the anchoring SIREN |
screen_parent | bool | no | Screen the ultimate parent too; default true |
Give exactly one entity key, siren or lei — zero or two is a 400.
Both keys pre-flight the same company. An lei= entry whose GLEIF record
declares a SIREN that really exists in the register reopens all the French
blocks (events, Peppol, derived VAT); query.resolved_siren says which one. A
foreign entity with no French registration gets those blocks as
not_applicable — never as a gap, and never as a silent empty.
screen_parent=falsedoes not block anok. Your explicit choice makes the blocknot_requested, which is not a coverage gap. Not knowing whether a parent exists is a different thing: that isunavailable, and it does forbidok. The check you waived is still named in the answer when a parent is actually declared — as a notice, which weighs nothing on the verdict.
200 response — UnifiedResponse
{
"data": {
"query": { "key": "siren", "value": "…", "screen_parent": true, "iban_submitted": false },
"subject": { "name": "…", "siren": "…", "lei": "…" },
"verdict": "ok",
"verdict_reasons": [],
"blocks": { "identity": { … }, "events": { … }, … },
"limits": "This verdict is a deterministic decision table …"
},
"provenance": {
"source": "insee-sirene",
"fetched_at": "2026-07-30T19:10:02Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-30T00:00:00Z" }
}
}
The root provenance reflects the identity foundation — the register the
subject was resolved from. The dates that matter for each check are on each
block: the snapshots are dated differently and the VAT check is live.
Shape of a block
{
"coverage": "complete",
"data": { … },
"provenance": { "source": "dila-bodacc", "fetched_at": "…", "freshness": { "kind": "snapshot", "as_of": "2026-07-26T00:00:00Z" } }
}
A block that got no answer carries neither data nor provenance —
only its coverage, a short stable reason and
retryable. The gap is stated, never
filled. A block can also be degraded: coverage: "unavailable" with a
reason, and the data it did obtain served in full — the answer it owes the
verdict is missing, not the facts (see the insolvency flag below).
coverage | Meaning | Effect on the verdict |
|---|---|---|
complete | The question got its answer | none |
unavailable | It did not — missing dataset, internal service unreachable, member state down and no firm VAT verdict to fall back on (degraded data may still be served). Always carries retryable | forbids ok |
not_applicable | There was nothing to check — no French register for a foreign entity, no declared parent, no VAT number | none |
not_requested | You waived the check (screen_parent=false) | none |
Verdicts and reason codes
| Verdict | Rule | Block | code |
|---|---|---|---|
stop | Legal unit ceased (Sirene) | identity | entity_ceased |
stop | Entity INACTIVE (GLEIF) | identity | entity_inactive |
stop | Published deregistration | events | deregistered |
stop | Open insolvency proceeding, published as such (flag strictly true) | events | open_insolvency_announcement |
stop | Sanctions hit on the company | name_screening | sanctions_hit |
stop | Sanctions hit on the ultimate parent | parent_screening | parent_sanctions_hit |
stop | The bank check itself returned stop | bank | bank_preflight_stop |
review | Sanctions weak_hit on the company / on the parent | name_screening / parent_screening | weak_sanctions_hit / parent_weak_sanctions_hit |
review | VAT number firmly answered invalid by the member state | vat | vat_number_invalid |
review | Gazetted legal event less than 365 days old | events | recent_legal_event |
review | Subject’s jurisdiction under FATF counter-measures | identity | high_risk_jurisdiction |
review | Parent’s jurisdiction under FATF counter-measures | group | parent_high_risk_jurisdiction |
review | LEI registration LAPSED | group | lei_registration_lapsed |
review | The bank check itself returned review | bank | bank_preflight_review |
insufficient_coverage | No signal, but at least one block is unavailable | the block concerned | coverage_unavailable (detail = its reason; the block says whether it is retryable) |
ok | No signal and full coverage on every requested block | — | — |
A reason says what it weighed: severity
Every entry of verdict_reasons carries severity — stop, review,
gap or notice. It is exactly the Verdict column of the table above, made
readable by machine, and the reasons arrive sorted, most severe first. The
verdict is the maximum of the severities served and nothing else, so you can
recompute it without knowing our rules by heart.
severity | What the reason does to the verdict |
|---|---|
stop | The verdict is stop |
review | The verdict is at least review |
gap | A check could not be settled → at least insufficient_coverage |
notice | Nothing — the verdict stays what it would have been without it |
A notice weighs nothing, and an ok can carry one
Two reasons are notices today: parent_screening_skipped_by_caller, the
check you waived, and expected_name_not_checkable, which belongs to
POST /certify/batch — the
register publishes no legal name, so there is nothing to compare. Both say the
same thing: a requested check did not happen, and neither is a reproach
aimed at the company.
When an ultimate parent is declared and you passed screen_parent=false, the
answer names it rather than staying silent:
{ "block": "parent_screening",
"code": "parent_screening_skipped_by_caller",
"severity": "notice",
"detail": "INTERNATIONAL BUSINESS MACHINES CORPORATION" }
Nothing is pushed when no parent is declared (not_applicable) or when the
control chain is unknown (unavailable, which already forbids ok): there was
nothing to skip.
An
okis not the promise that every requested check happened. It is the promise that none of them raised a reason for reservation. An agent that filters onverdict == "ok"without readingverdict_reasonswill not see what could not be verified.
severity is served on every reason in the extracts on this page; it is the one
field here that ships with the next image rather than the one deployed today.
Every other value in those extracts is as served.
Not every gap is the same: retryable
Every block in coverage: "unavailable" carries retryable — would asking
the same question later fill this gap?
retryable | What it is | Typical reasons |
|---|---|---|
true | A fact about a third-party service or about us, never about the company: the member state throttled us, took too long, or our own call budget declined to emit. | MS_MAX_CONCURRENT_REQ, MS_UNAVAILABLE, SERVICE_UNAVAILABLE, GLOBAL_MAX_CONCURRENT_REQ, timeout, network_unreachable, http_5xx, local_vies_budget |
false | A structural fact: closing it takes an ingestion or a datum that does not exist — not patience. | no_lei_declared_for_siren, no_current_lei_for_siren, ambiguous_lei_for_siren, siren_to_lei_bridge_unavailable, group_unavailable, no_name_to_screen, bodacc_dataset_unavailable, peppol_dataset_unavailable, gleif_dataset_unavailable, undetermined_latest_notice, vat_number_not_derivable, not_configured, unexpected_response, http_4xx |
The field is absent everywhere else: a full coverage has nothing to replay,
and a not_applicable / not_requested is not a gap. A reason never seen
before is qualified false by default — inviting a retry on a gap that does
not close would make an agent pay for a loop of calls returning the same
answer, where the opposite mistake costs it, at worst, an early give-up.
The vat block carries the qualification twice: on the block
(blocks.vat.retryable) and inside its data (blocks.vat.data.retryable, the
exact shape of GET /company/vat). They cannot
diverge — the second is the source of the first.
retry_after_secs — advice, never a promise
When the verdict is insufficient_coverage and every gap is retryable, the
response carries retry_after_secs (30) at the root of data. It is a
field of the answer, not an HTTP Retry-After header, and it is
indicative: it is derived from what we measure — a French check answers in 4 to
7 seconds, the interactive queue of our VIES budget hands back within 500 ms —
and VIES, a free shared public service, owes us nothing.
A single structural gap removes the advice, even when every other one would
replay: the extra round of calls would come back with the same
insufficient_coverage, billed as many times. It is block by block, through
retryable, that an agent then knows what is worth re-asking.
The field is absent on ok, review and stop: replaying does not change a
signal.
The two gaps, side by side
Both extracts below are real, captured on the production store and against the live VIES on 16 August 2026.
A transient gap, and it says so — GET /preflight/supplier?siren=380129866
(ORANGE). VIES FR did not answer within the call budget:
{
"verdict": "insufficient_coverage",
"verdict_reasons": [
{ "block": "vat", "code": "coverage_unavailable", "severity": "gap", "detail": "timeout" }
],
"retry_after_secs": 30,
"blocks": {
"vat": {
"coverage": "unavailable", "reason": "timeout", "retryable": true,
"data": { "vat_number": "FR89380129866", "syntax_valid": true,
"status": "unverifiable", "reason": "timeout",
"retryable": true, "coverage": "syntax_only" }
}
}
}
The same call, with VIES nominal, returns ok in 4.1 s. That swing was already
happening; what is new is that the first answer now announces the second.
A structural gap alongside a transient one — no advice —
GET /preflight/supplier?siren=444444442 (4X4 JET SET, no LEI declared), VIES
throttled at the same moment. Three gaps, one of them replayable:
{
"verdict": "insufficient_coverage",
"retry_after_secs": null,
"blocks": {
"vat": { "coverage": "unavailable",
"reason": "MS_MAX_CONCURRENT_REQ", "retryable": true },
"group": { "coverage": "unavailable",
"reason": "no_lei_declared_for_siren", "retryable": false },
"parent_screening": { "coverage": "unavailable",
"reason": "group_unavailable", "retryable": false }
}
}
(retry_after_secs is not served as null here — it is absent. It is
written out to keep the extract readable.) Asking again would close the vat
gap and leave the other two exactly as they are: the verdict would not move,
the invoice would.
A cached VAT verdict is complete coverage
VIES quotas are per member state, not per caller: a register can throttle a
pre-flight because the rest of Europe is querying it at that second. Losing an
ok over that would blame the supplier for an upstream outage, so when the
live check fails and we hold a firm verdict on that number from the last 24
hours, the vat block serves it instead of going unavailable:
"vat": {
"coverage": "complete",
"data": {
"vat_number": "FR13542107651",
"syntax_valid": true,
"status": "valid",
"name": "SA ENGIE",
"derived_from_siren": "542107651",
"checked_at": "2026-07-31T09:12:44Z",
"served_from": "cache",
"cached_age_secs": 8143
},
"provenance": { "source": "vies + pure-computation", "freshness": { "kind": "cached", "age_secs": 8143 } }
}
A firm verdict is an answer, so the coverage is complete and ok
becomes reachable again under throttling. Read the counterpart before you act
on it: such an ok can rest on a VAT verdict a few hours old. The age is in
the block - age_secs in the provenance, cached_age_secs in the data - so an
agent that wants a stricter rule can apply its own and re-check the number
itself.
Two things this does not change. The verdict is never invented: it is a
firm valid / invalid really obtained from VIES, checked_at rewound to the
original check, and an unverifiable never enters the cache - so cached
always means a firm verdict, and the unverifiable /
coverage: "syntax_only" gap stays exactly what it was, the case where we have
never had a verdict on this number. And the cache only helps on repetition:
a first pre-flight on a given number still pays the member state’s full
latency - measured on 12 August 2026, 7 to 10 seconds for a French VAT
number, and a few tens of milliseconds to a few seconds elsewhere, with wide
variation between two calls on the same number
(the detail).
An undetermined insolvency flag is a gap, never a stop
open_insolvency_announcement carries a stop only when the BODACC flag is
strictly true. That flag is
nullable:
the gazette publishes insolvency announcements whose label qualifies nothing —
a catch-all that covers, among other things, the homologation of a
conciliation agreement, a preventive and amicable procedure that opens no
collective proceeding.
When no announcement in the timeline qualifies, the events block is served
degraded: coverage: "unavailable", reason: "undetermined_latest_notice",
with the whole timeline still in data — announcement labels, courts, dates and
texts included.
"events": {
"coverage": "unavailable",
"reason": "undetermined_latest_notice",
"data": { "…": "the complete timeline, served" },
"provenance": { "source": "dila-bodacc", "freshness": { "kind": "snapshot", "as_of": "2026-07-26T00:00:00Z" } }
}
Read the consequence carefully, because it is the whole point of the third
state. Not knowing is not “nothing to report”: the block is a gap, so it
forbids ok and adds
{ "block": "events", "code": "coverage_unavailable", "severity": "gap", "detail": "undetermined_latest_notice" }
to verdict_reasons — on its own, that makes the verdict insufficient_coverage.
And not knowing is not an open insolvency proceeding either: no stop is
raised on this block. The question stays open, named, and auditable against the
announcements served right below it.
Examples
All four examples below are captured from the production service, on the
snapshots dated in their own blocks (Sirene 2026-06-30, BODACC 2026-07-26,
Peppol directory 2026-07-28, GLEIF 2026-07-29, designation lists
2026-07-28; the VAT check is live).
ok — every requested check answered, none of them raised a signal
GET /preflight/supplier?siren=542107651
{
"query": { "key": "siren", "value": "542107651", "screen_parent": true, "iban_submitted": false },
"subject": { "name": "ENGIE", "siren": "542107651", "lei": "LAXUQCHT4FH58LRZDY46" },
"verdict": "ok",
"verdict_reasons": [],
"blocks": {
"identity": {
"coverage": "complete",
"data": { "siren": "542107651", "name": "ENGIE", "status": "active", "legal_form": { "code": "5599", "label": "SA à conseil d'administration (s.a.i.)" }, "…": "…" },
"provenance": { "source": "insee-sirene", "freshness": { "kind": "snapshot", "as_of": "2026-06-30T00:00:00Z" } }
},
"vat": {
"coverage": "complete",
"data": {
"vat_number": "FR13542107651",
"syntax_valid": true,
"status": "valid",
"name": "SA ENGIE",
"derived_from_siren": "542107651",
"checked_at": "2026-07-30T19:10:00Z"
},
"provenance": { "source": "vies + pure-computation", "freshness": { "kind": "live" } }
},
"peppol": {
"coverage": "complete",
"data": { "reachable": false, "participants": [], "coverage": { "directory_as_of": "2026-07-28", "note": "reachability reflects the public Peppol directory export dated directory_as_of, not a live SMP lookup" } },
"provenance": { "source": "peppol-directory", "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" } }
},
"group": {
"coverage": "complete",
"data": {
"lei": "LAXUQCHT4FH58LRZDY46",
"registration_status": "ISSUED",
"legal_country": "FR",
"ultimate_parent": { "status": "reporting_exception", "link": null, "exception": { "category": "ULTIMATE_ACCOUNTING_CONSOLIDATION_PARENT", "reasons": ["NATURAL_PERSONS"] } },
"limits": "Relationships are self-declared by the entities to GLEIF and describe accounting consolidation …"
},
"provenance": { "source": "gleif", "freshness": { "kind": "snapshot", "as_of": "2026-07-29T00:00:00Z" } }
},
"name_screening": {
"coverage": "complete",
"data": { "query": { "name": "ENGIE", "normalized": "engie" }, "verdict": "no_hit", "hits": [], "lists_screened": [ { "list": "EU-FSF", "entities": 6017 }, { "list": "OFAC-CONS", "entities": 481 }, { "list": "OFAC-SDN", "entities": 19157 }, { "list": "UN", "entities": 1011 } ], "total_matches": 0 },
"provenance": { "source": "ofac-sdn+ofac-consolidated+eu-fsf+un-sc", "freshness": { "kind": "snapshot", "as_of": "2026-07-28T00:00:00Z" } }
},
"parent_screening": { "coverage": "not_applicable", "reason": "no_declared_ultimate_parent_to_screen" }
}
}
parent_screening is not_applicable here, not a gap: GLEIF publishes a
reporting exception — the consolidating parents are natural persons — so there
is no parent name to screen. That is an answer, and an answer does not block
ok.
Note also peppol.reachable: false. Not being registered in the Peppol
directory is a fact about e-invoicing, not a signal about the supplier: it
weighs nothing on the verdict.
review — the parent is the reason this endpoint exists
GET /preflight/supplier?siren=316037753
{
"subject": { "name": "IBM FRANCE FINANCEMENT", "siren": "316037753", "lei": "0XKTQGPTA3P542CII630" },
"verdict": "review",
"verdict_reasons": [
{ "block": "parent_screening", "code": "parent_weak_sanctions_hit", "severity": "review", "detail": "partial_tokens" }
],
"blocks": {
"identity": { "coverage": "complete", "data": { "name": "IBM FRANCE FINANCEMENT", "status": "active", "…": "…" } },
"events": { "coverage": "complete", "data": { "events": [], "total": 0, "flags": { "has_open_insolvency_announcement": false, "is_deregistered": false } } },
"vat": { "coverage": "complete", "data": { "vat_number": "FR60316037753", "status": "valid", "name": "SAS IBM FRANCE FINANCEMENT" } },
"group": {
"coverage": "complete",
"data": {
"lei": "0XKTQGPTA3P542CII630",
"legal_country": "FR",
"ultimate_parent": {
"status": "declared",
"link": {
"lei": "VGRQXHF3J8VDLUA7XE92",
"entity": { "legal_name": "INTERNATIONAL BUSINESS MACHINES CORPORATION", "legal_country": "US" },
"relationship_status": "ACTIVE",
"period": { "start": "1988-07-08" }
}
}
}
},
"name_screening": { "coverage": "complete", "data": { "query": { "name": "IBM FRANCE FINANCEMENT" }, "verdict": "no_hit", "hits": [] } },
"parent_screening": {
"coverage": "complete",
"data": {
"query": { "name": "INTERNATIONAL BUSINESS MACHINES CORPORATION", "normalized": "international business machines" },
"verdict": "weak_hit",
"hits": [ { "id": "OFAC-SDN:45091", "list_source": "OFAC-SDN", "match_type": "partial_tokens", "…": "…" } ]
}
}
}
}
The company itself is spotless: active, VAT valid, no gazetted event, no_hit
on the lists. The review comes entirely from one level up — the declared
ultimate parent, screened by name, on a partial token overlap. A weak_hit on
the parent is a review, never a stop: a token overlap is a lead to
check, not a designation.
Another review you will meet often is the gazette: a supplier with a recent
BODACC entry (for instance a sale published on 2026-06-05) comes back
review with recent_legal_event — a change worth reading, not a red flag.
stop — the register says it plainly
GET /preflight/supplier?siren=542097704
{
"subject": { "name": "RECYLEX SA", "siren": "542097704", "lei": "969500N70Q1WX29Z2675" },
"verdict": "stop",
"verdict_reasons": [
{ "block": "events", "code": "open_insolvency_announcement", "severity": "stop" },
{ "block": "vat", "code": "vat_number_invalid", "severity": "review", "detail": "FR48542097704" },
{ "block": "group", "code": "lei_registration_lapsed", "severity": "review" }
],
"blocks": {
"identity": { "coverage": "complete", "data": { "name": "RECYLEX SA", "status": "active", "lei": { "registration_status": "LAPSED", "verified_against_sirene": true } } },
"events": {
"coverage": "complete",
"data": {
"events": [
{ "family": "insolvency", "nature": "Dépôt de l'état des créances", "effective_on": "2023-02-23", "published_on": "2024-06-02", "court": "Greffe du Tribunal de Commerce de Paris" },
{ "family": "insolvency", "nature": "Jugement de conversion en liquidation judiciaire", "effective_on": "2022-11-09", "published_on": "2022-12-04", "court": "Greffe du Tribunal de Commerce de Paris" },
{ "family": "insolvency", "nature": "Jugement d'ouverture d'une procédure de redressement judiciaire", "effective_on": "2022-05-05", "published_on": "2022-05-22", "court": "Greffe du Tribunal de Commerce de Paris" }
],
"total": 6,
"flags": { "has_open_insolvency_announcement": true, "is_deregistered": false },
"coverage": { "window": { "from": "2008-01-01", "to": "2026-07-26" } }
},
"provenance": { "source": "dila-bodacc", "freshness": { "kind": "snapshot", "as_of": "2026-07-26T00:00:00Z" } }
},
"vat": { "coverage": "complete", "data": { "vat_number": "FR48542097704", "syntax_valid": true, "status": "invalid" } },
"name_screening": { "coverage": "complete", "data": { "verdict": "no_hit", "hits": [] } },
"parent_screening": { "coverage": "not_applicable", "reason": "no_declared_ultimate_parent_to_screen" }
}
}
Three reasons, most severe first, each attached to its block and each auditable
in the block right below it: the gazette shows the judgments and their courts,
VIES answers invalid on the derived VAT number, GLEIF has let the LEI
registration lapse. The most recent insolvency announcement here is a registry
filing whose label qualifies nothing (“Dépôt de l’état des créances”) — the flag
is true all the same, read on the conversion judgment below it: a
non-qualifying announcement never extinguishes the one it follows. Note that Sirene still reports the legal unit as active —
the composite does not reconcile the registers, it reports each one and lets
the most severe fact decide.
insufficient_coverage — clean, but the control chain is unknowable
GET /preflight/supplier?siren=500569405
{
"subject": { "name": "DECATHLON FRANCE", "siren": "500569405", "lei": null },
"verdict": "insufficient_coverage",
"verdict_reasons": [
{ "block": "group", "code": "coverage_unavailable", "severity": "gap", "detail": "no_lei_declared_for_siren" },
{ "block": "parent_screening", "code": "coverage_unavailable", "severity": "gap", "detail": "group_unavailable" }
],
"blocks": {
"identity": { "coverage": "complete", "data": { "name": "DECATHLON FRANCE", "status": "active", "legal_form": { "code": "5710", "label": "SAS, société par actions simplifiée" } } },
"events": { "coverage": "complete", "data": { "flags": { "has_open_insolvency_announcement": false, "is_deregistered": false } } },
"vat": { "coverage": "complete", "data": { "vat_number": "FR11500569405", "status": "valid", "name": "SASU DECATHLON FRANCE" } },
"peppol": { "coverage": "complete", "data": { "reachable": false, "participants": [] } },
"group": { "coverage": "unavailable", "reason": "no_lei_declared_for_siren" },
"name_screening": { "coverage": "complete", "data": { "verdict": "no_hit", "hits": [] } },
"parent_screening": { "coverage": "unavailable", "reason": "group_unavailable" }
}
}
Active, VAT valid, nothing in the gazette, nothing on the lists — and still not
ok. No LEI is declared for this SIREN, so the control chain cannot be
established, so the parent cannot be screened. The gap cascades and is
named (group_unavailable), rather than being reported as “no parent”. Most
French companies have no LEI: this verdict is the normal, honest answer for
them, and it tells your agent exactly which question is still open.
The four reasons the group block can be unavailable
They each say a different thing and are never folded into one another:
reason / detail | What it says |
|---|---|
no_lei_declared_for_siren | no LEI declares this SIREN — a structural limit of the LEI, and the case of the vast majority of French companies |
ambiguous_lei_for_siren | several current records compete: the data does not tell them apart (1 SIREN in the reference) |
no_current_lei_for_siren | LEIs do declare this SIREN, but none is a current record (all replaced, retired or lapsed) — nothing to arbitrate (1 SIREN) |
siren_to_lei_bridge_unavailable / gleif_dataset_unavailable | the served image does not carry the SIREN→LEI bridge, or the GLEIF dataset itself |
A SIREN carrying several LEI records is not, on its own, a coverage hole:
the anchor is the canonical record — a DUPLICATE, ANNULLED or MERGED
is one GLEIF itself took out of circulation, not a contender, and among the
candidates left the one GLEIF declares current (ISSUED) wins over a stale
record of the same entity. 175 of the 177 multi-record SIREN are settled that
way, AXA (572093920) among them. The raw list of records stays readable on
GET /company/lei?siren=, which does not settle
anything — that is its contract.
Two variants worth knowing
- Entering by
lei=on a foreign entity —?lei=VGRQXHF3J8VDLUA7XE92returns identity from GLEIF andevents,vatandpeppolasnot_applicable(no_french_registration,no_vat_number_to_check). The verdict is decided on what does apply, never dragged down by registers that cannot concern the subject. - Adding
iban=adds abankblock that carries a whole/preflight/paymentanswer — with the register name of the supplier used as the beneficiary name, so the name block of the bank check is genuinely screened rather than skipped.
Errors
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Zero or two entity keys, or an uninterpretable screen_parent / VAT number |
| 400 | INVALID_INPUT | Unknown query parameter name — the message names it, suggests the nearest expected one when there is a close match, and lists what the route accepts (siren, lei, iban, vat_number, screen_parent). Not charged. |
| 400 | INVALID_CHECKSUM | siren failing Luhn, or lei failing its ISO 17442 check digits |
| 404 | NOT_FOUND | Well-formed key unknown to the register — unbilled |
| 503 | DATA_UNAVAILABLE | The queried register’s dataset was never ingested |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "code": "NOT_FOUND", "error": "siren not found in the Sirene register" }
An unusable answer is never billed; a stop, a review and an
insufficient_coverage all are, because all three are answers.
Three things about that unknown-parameter 400, said here because this is the
route where a silently ignored name cost the most — ?screen_parents=false
(plural) used to be dropped without a word and returned review where
?screen_parent=false returns ok, so the caller paid for a verdict that
answers a different question than the one asked:
- Case matters.
?SIREN=is refused and named, where it previously passed for a plainly missing parameter, which says nothing about the typo. - The refusal comes first, before the VIES check and before any internal call, so a misspelled name costs neither latency nor quota.
- Unknown values were already refused. What changes is that unknown names now are too: this restores the symmetry, it is not a new severity.
See also
GET /preflight/payment— the same idea on a transfer: IBAN, bank, jurisdiction and beneficiary name, in one call.GET /company/resolve,GET /company/events,GET /company/vat,GET /company/peppol,GET /company/ownershipandGET /screen/entity— the unit checks this endpoint runs, if you would rather orchestrate them yourself.GET /company/report— the same registers assembled as a report, without the verdict.- For agents — discovery surfaces, the live
/catalogand how settlement works.