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
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.

The verdict is always exactly the maximum severity among the reasons, and every reason carries its block, a stable code 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.

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.

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 is not complete carries neither data nor provenance — only its coverage and a short stable reason. The gap is stated, never filled.

coverageMeaningEffect on the verdict
completeThe question got its answernone
unavailableIt did not — missing dataset, internal service unreachable, member state down (degraded data may still be served)forbids 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 with no published closureeventsopen_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)
okNo signal and full coverage on every requested block

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", "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" },
    { "block": "vat", "code": "vat_number_invalid", "detail": "FR48542097704" },
    { "block": "group", "code": "lei_registration_lapsed" }
  ],
  "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. 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", "detail": "no_lei_declared_for_siren" },
    { "block": "parent_screening", "code": "coverage_unavailable", "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.

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_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.

See also