GET /preflight/supplier

Runs every check on a counterparty, in one call, before your agent onboards it, pays it or invoices it:

BlockQuestion it answersSource
identityDoes it exist, and is it still active?INSEE Sirene, or GLEIF
eventsInsolvency, deregistration, sales — what has been gazetted?BODACC
vatIs its EU VAT number valid right now?VIES, live (or our last firm verdict, dated)
peppolCan it receive an e-invoice?Peppol directory
groupWho declares control over it?GLEIF Level 2
name_screeningIs the company itself designated?OFAC SDN & Consolidated, EU FSF, UN
parent_screeningIs its ultimate parent designated?the same lists
bankIs its IBAN payable? (only when you pass iban)/preflight/payment

It returns one verdictok, 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 answer200 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

ok is impossible while any block is unavailable. 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_applicable is an answer; unavailable is 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 forbids ok.
  • 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 vat block serves our last firm verdict on that number, up to 24 hours old, with its age attached — see below.

Parameters

ParameterTypeRequiredDescription
sirenstringone of9-digit SIREN, Luhn-checked
leistringone of20-character LEI (ISO 17442 check digits)
ibanstringnoAdds the bank block. Absent means the block is absent — not an empty block
vat_numberstringnoVAT number of any member state; absent, it is derived from the anchoring SIREN
screen_parentboolnoScreen 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=false does not block an ok. Your explicit choice makes the block not_requested, which is not a coverage gap. Not knowing whether a parent exists is a different thing: that is unavailable, and it does forbid ok. 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).

coverageMeaningEffect on the verdict
completeThe question got its answernone
unavailableIt 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 retryableforbids ok
not_applicableThere was nothing to check — no French register for a foreign entity, no declared parent, no VAT numbernone
not_requestedYou waived the check (screen_parent=false)none

Verdicts and reason codes

VerdictRuleBlockcode
stopLegal unit ceased (Sirene)identityentity_ceased
stopEntity INACTIVE (GLEIF)identityentity_inactive
stopPublished deregistrationeventsderegistered
stopOpen insolvency proceeding, published as such (flag strictly true)eventsopen_insolvency_announcement
stopSanctions hit on the companyname_screeningsanctions_hit
stopSanctions hit on the ultimate parentparent_screeningparent_sanctions_hit
stopThe bank check itself returned stopbankbank_preflight_stop
reviewSanctions weak_hit on the company / on the parentname_screening / parent_screeningweak_sanctions_hit / parent_weak_sanctions_hit
reviewVAT number firmly answered invalid by the member statevatvat_number_invalid
reviewGazetted legal event less than 365 days oldeventsrecent_legal_event
reviewSubject’s jurisdiction under FATF counter-measuresidentityhigh_risk_jurisdiction
reviewParent’s jurisdiction under FATF counter-measuresgroupparent_high_risk_jurisdiction
reviewLEI registration LAPSEDgrouplei_registration_lapsed
reviewThe bank check itself returned reviewbankbank_preflight_review
insufficient_coverageNo signal, but at least one block is unavailablethe block concernedcoverage_unavailable (detail = its reason; the block says whether it is retryable)
okNo signal and full coverage on every requested block

A reason says what it weighed: severity

Every entry of verdict_reasons carries severitystop, 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.

severityWhat the reason does to the verdict
stopThe verdict is stop
reviewThe verdict is at least review
gapA check could not be settled → at least insufficient_coverage
noticeNothing — 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 ok is 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 on verdict == "ok" without reading verdict_reasons will 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?

retryableWhat it isTypical reasons
trueA 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
falseA 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 soGET /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 adviceGET /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 activethe 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 / detailWhat it says
no_lei_declared_for_sirenno LEI declares this SIREN — a structural limit of the LEI, and the case of the vast majority of French companies
ambiguous_lei_for_sirenseveral current records compete: the data does not tell them apart (1 SIREN in the reference)
no_current_lei_for_sirenLEIs 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_unavailablethe 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=VGRQXHF3J8VDLUA7XE92 returns identity from GLEIF and events, vat and peppol as not_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 a bank block that carries a whole /preflight/payment answer — 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

StatuscodeCase
400INVALID_INPUTZero or two entity keys, or an uninterpretable screen_parent / VAT number
400INVALID_INPUTUnknown 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.
400INVALID_CHECKSUMsiren failing Luhn, or lei failing its ISO 17442 check digits
404NOT_FOUNDWell-formed key unknown to the register — unbilled
503DATA_UNAVAILABLEThe queried register’s dataset was never ingested
500INTERNALInternal 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