# Due diligence on a French company: five registers, one call > One SIREN, five public registers — Sirene, BODACC, VIES, Peppol, GLEIF — each block dated with its own provenance, and no risk score anywhere. Published 2026-08-10 · Updated 2026-08-15 · HTML version: https://invoket.com/blog/due-diligence-on-a-french-company-in-one-settlement --- An agent about to invoice, pay or onboard a French company needs five facts that live in five different registers: is the legal unit real and still registered, has anything been gazetted against it, is its VAT number valid right now, can it receive an e-invoice, and which group consolidates it. [`GET /company/report`](/docs/api/company-report) answers all five for one SIREN in **one settlement**. The bundling is the convenience; the contract is something else — **each block carries its own provenance and its own date**, and the response never fuses them into a verdict or a score. That distinction is the whole point. The five registers do not agree, do not refresh on the same clock, and are not equally authoritative on the same question. A report that flattened them into one green light would be lying about its own inputs. ## "Active" is not the same as "solvent" INSEE Sirene tells you whether a legal unit is registered and not closed. It does **not** tell you whether a commercial court has opened a liquidation. Those are two registers answering two different questions, and the gap between them is not a rounding error: the Banque de France counted **70,803 business failures** in France over the twelve months to June 2026, in its release of 7 August 2026, up from roughly 70,117 a month earlier ([via Reuters](https://www.boursorama.com/actualite-economique/actualites/france-les-defaillances-d-entreprises-progressent-legerement-en-juin-f3c75143b1d4599fe13d8ebea357f167)). An agent that reads Sirene alone will read `status: "active"` on a company in judicial liquidation and conclude it is fine to invoice. The report exists so that both facts arrive in the same response, side by side, each pointing at the register that published it. ## A capture from production, taken today `GET /company/report?siren=321875205` — SAN MARINA, a French shoe retailer whose liquidation was gazetted in 2023. Abridged from the real response, captured 2026-08-10: ```json { "data": { "siren": "321875205", "identity": { "name": "SAN MARINA", "status": "active", "provenance": { "source": "insee-sirene", "freshness": { "kind": "snapshot", "as_of": "2026-07-31T00:00:00Z" } } }, "events": { "total": 36, "flags": { "has_open_insolvency_announcement": true, "is_deregistered": false, "latest_event": { "family": "sale", "date": "2024-02-01" } }, "provenance": { "source": "dila-bodacc", "freshness": { "kind": "snapshot", "as_of": "2026-08-02T00:00:00Z" } } }, "vat": { "vat_number": "FR36321875205", "syntax_valid": true, "status": "unverifiable", "reason": "MS_MAX_CONCURRENT_REQ", "coverage": "syntax_only", "derived_from_siren": "321875205", "provenance": { "source": "pure-computation", "freshness": { "kind": "live" } } }, "peppol": { "reachable": false, "participants": [], "provenance": { "source": "peppol-directory", "freshness": { "kind": "snapshot", "as_of": "2026-08-04T00:00:00Z" } } }, "lei": { "lei": "969500HF9MVOFPWK0A17", "legal_name": "SAN MARINA", "entity_status": "ACTIVE", "registration_status": "LAPSED", "ultimate_parent": { "status": "reporting_exception", "link": null, "exception": { "category": "ULTIMATE_ACCOUNTING_CONSOLIDATION_PARENT", "reasons": ["NO_LEI"] } }, "provenance": { "source": "gleif", "freshness": { "kind": "snapshot", "as_of": "2026-08-09T00:00:00Z" } } }, "summary": { "status": "active", "has_open_insolvency_announcement": true, "vat_status": "unverifiable", "peppol_reachable": false, "lei": "969500HF9MVOFPWK0A17" }, "coverage": { "complete": true } } } ``` Read the dates before you read the facts. **Four different snapshot dates and one live check**, in a single response: Sirene as of 31 July, BODACC as of 2 August, Peppol as of 4 August, GLEIF as of 9 August, VIES attempted at call time. Nothing here is smoothed under a common `fetched_at`, because nothing here was actually established on the same day. And then read the contradiction the report refuses to resolve: **Sirene says `active`, BODACC says the insolvency proceeding is open.** Both are true statements about their own register. The report states both and lets the caller decide, rather than adjudicating which register is right. ## Provenance per block is the contract | Block | Register | Freshness | The question it answers | |------------|-----------------------------------|---------------------|-------------------------| | `identity` | INSEE Sirene | month-end snapshot | Does this legal unit exist, and is it registered as active? | | `events` | BODACC / DILA | extraction snapshot | What has been gazetted — insolvency, deregistration, sales? | | `vat` | VIES (European Commission) | **live** | Is this VAT number valid right now? | | `peppol` | public Peppol Directory export | dated export | Is this company listed as reachable on Peppol? | | `lei` | GLEIF Golden Copy | dated image | What is the global identity, and who consolidates it? | The root `provenance` of the response mirrors the `identity` foundation only. That is deliberate: there is no single date at which this report was true, so the endpoint does not invent one. Every block's shape is exactly the shape of its unit endpoint — [`/company/resolve`](/docs/api/company-resolve), [`/company/events`](/docs/api/company-events), [`/company/vat`](/docs/api/company-vat), [`/company/peppol`](/docs/api/company-peppol), [`/company/lei`](/docs/api/company-lei) — so the field-level documentation of a block *is* the documentation of that endpoint. ## The VAT block was throttled, and that is still a `200` Look again at the `vat` block above. The live VIES check did not answer: `MS_MAX_CONCURRENT_REQ`, the member state's concurrency limit. What the endpoint does with that is the interesting part. The block comes back `status: "unverifiable"` with the reason named, `coverage: "syntax_only"`, and its **provenance downgraded to `pure-computation`** — the only thing actually established was the local syntax and check-digit computation, so that is the only thing claimed. `summary` propagates `vat_status: "unverifiable"`. The other four blocks are intact and `coverage.complete` stays `true`, because the block *was* served, honestly degraded. This is not an edge case to design around. VIES routes each query to the member state that owns the data, and each national database has its own outages and maintenance windows; "member state service unavailable" is a normal outcome of the system rather than an exception ([Your Europe / VIES](https://europa.eu/youreurope/business/taxation/vat/check-vat-number-vies/index_en.htm)). The quotas are per member state, not per caller — a check can fail because the rest of Europe is querying that register at that second. Two things follow, and an agent should branch on both: - **`unverifiable` is not `invalid`.** One is the absence of a verdict, the other is a verdict. Treating a throttle as a negative signal is how automated screening starts rejecting good counterparties. - **`unverifiable` is not the only degraded shape.** When the member state does not answer but a *firm* verdict on that exact number is already held from the last 24 hours, the block serves that verdict instead — `status` still `valid` or `invalid`, with `served_from: "cache"`, `cached_age_secs`, and a `freshness.kind` of `cached`. That is a complete, dated answer, not a fabricated one. The full contract for both is on [`/company/vat`](/docs/api/company-vat). A `5xx` never appears in either path. Staleness is not an error. ## There is no risk score, on purpose `summary` is derived **mechanically** from the blocks that were served: | Field | From | Value | |------------------------------------|---------|-------| | `status` | Sirene | `active` / `ceased` | | `has_open_insolvency_announcement` | BODACC | boolean — **absent when undetermined** | | `vat_status` | VIES | `valid` / `invalid` / `unverifiable` | | `peppol_reachable` | Peppol | boolean | | `lei` | GLEIF | the LEI of the canonical record attached to the SIREN | No rating, no recommendation, no number to threshold. And the insolvency flag is **nullable**: the gazette publishes announcements whose label settles nothing, so when the block was served but the state of the proceeding is undetermined, the field is simply **absent** rather than folded back to `false`. An unknown fact never becomes a negative one. When it matters to your logic, read the `events` block and its timeline, not the summary line. If what you want is a decision rather than facts, [`GET /preflight/supplier`](/docs/api/preflight-supplier) is the endpoint that returns one — `ok`, `review`, `stop` or `insufficient_coverage`, with sanctions screening of the company *and* of its ultimate parent. The two are complementary, not redundant: the report is for the audit trail and for workflows that apply their own policy; the pre-flight is for workflows that want a verdict they can branch on. ## What the GLEIF block says, and the two things it does not The capture above shows a company whose LEI record reads `entity_status: "ACTIVE"` and `registration_status: "LAPSED"`. Those are different fields answering different questions, and conflating them is a real mistake. **`LAPSED` is about the record, not the company.** It means the entity did not renew and re-certify its LEI registration by its next renewal date. GLEIF is explicit that "a lapsed LEI remains valid" and that lapsed "simply signals that an organization is behind schedule as regards renewal, i.e. re-validation of its information against third party sources" ([GLEIF, on timely renewal](https://www.gleif.org/en/newsroom/blog/data-quality-and-risk-management-the-importance-of-timely-renewal-of-legal-entity-identifiers)). It is a statement about the freshness of a reference record — which is exactly the kind of thing this endpoint refuses to hide behind a summary field. **`ultimate_parent` is accounting consolidation, not beneficial ownership.** GLEIF Level 2 records the entity's direct and ultimate *accounting consolidating* parent — the entity that consolidates it in its financial statements — self-declared by the entity itself, with **no ownership percentage** published ([GLEIF, Level 2 Data: Who Owns Whom](https://www.gleif.org/en/lei-data/access-and-use-lei-data/level-2-data-who-owns-whom)). It is an accounting definition of control, not an AML one. The response repeats this in `data.limits` on every single call rather than burying it in documentation, and the absence of a declared parent is never evidence of an absence of control. For the full chain — direct parent, ultimate parent, subsidiaries — see [`/company/ownership`](/docs/api/company-ownership). Note also the shape of the "no parent" case here: `status: "reporting_exception"` with the official category and reason `NO_LEI` — the parent exists but has no LEI, so GLEIF cannot publish the link. That is an **answer**, dated and sourced, not an empty block. The same holds one level down: a company with no LEI at all comes back with `total_matches: 0`, which is the answer "this company has no LEI" — the situation of the large majority of French companies — and not an unavailability. ## Peppol reachability is a directory fact, not French e-invoicing readiness The timing makes this worth stating precisely. From **1 September 2026**, French in-scope businesses must use an approved platform (*plateforme agréée*) to transmit and receive electronic invoices and to report transaction and payment data to the administration; issuance obligations are phased, with large and mid-sized enterprises in 2026 and SMEs on 1 September 2027 ([impots.gouv.fr](https://www.impots.gouv.fr/facturation-electronique-et-plateformes-agreees)). Any agent onboarding French suppliers this quarter is going to be asked whether a counterparty can receive an invoice electronically. The `peppol` block answers a narrower question than that, and says so. It reports whether the company appears in a **dated export of the public Peppol Directory**, with its participant identifiers and declared document types. It is: - **not a live SMP lookup** — the answer reflects the export date carried in `coverage.directory_as_of`; - **not a statement about the French reform** — being absent from the Peppol Directory is not a finding about a company's registration with an approved platform, and `reachable: false` is a dated directory fact, nothing more. `reachable: false` on SAN MARINA above means the company is not listed in that export. It is a paid, dated, honest answer — and it is not a verdict about anything else. ## `include=` gives you a fully offline report VIES is the service's only outbound call. Drop it and the whole report is served from local snapshots: ```http GET /company/report?siren=321875205&include=identity,events,peppol ``` ```json { "summary": { "status": "active", "has_open_insolvency_announcement": true, "peppol_reachable": false }, "coverage": { "complete": false, "excluded": ["vat", "lei"] } } ``` That request returns in milliseconds instead of the few hundred a live VIES check costs, and — this is the part that matters for an agent — `coverage.excluded` **names what is missing**. `summary.vat_status` is absent because the block was excluded, and the response says so explicitly. There is no silent absence anywhere in this endpoint: a block is complete, excluded by `include=`, or listed in `coverage.unavailable`. `identity` is always served, as the base. ## Billing: an empty report is never paid for The x402 golden rule is that the agent pays for the answer to its question, so the mapping is strict in both directions: | Case | Result | |------|--------| | Known SIREN | `200`, billed — including a degraded block or an `include=` subset | | Well-formed SIREN unknown to Sirene | `404 NOT_FOUND`, **not billed** — no empty report is ever served | | Missing / malformed `siren`, failed Luhn check, unknown `include=` value | `400`, not billed | A counterparty in liquidation is a **useful** answer and is billed — the value of the call is the finding, not the reassurance. An identifier no register knows is not an answer, and the gateway does not settle it. That 404 is the deliberate difference with the unit endpoints, where an *absence* — no gazette entry, not on Peppol — is itself a legitimate paid `200`. A report with no identity has no object. ## Wiring it up 1. **Anchor on the SIREN.** This route takes `siren` only — no `siret=`, no `vat=`, no search by name. 2. **Choose your blocks.** Omit `include=` for all five; pass a subset when you only need part of the picture, or when you want the zero-network path. 3. **Branch on blocks, not on the summary**, wherever the distinction between "false" and "undetermined" changes your decision. 4. **Persist the whole response, not a boolean.** Six months later, "why did the agent invoice this company" is answerable from the stored blocks and their `as_of` dates. That is the reason provenance is per block. 5. **Discover, then pay.** The usual x402 cycle — discover → `402` → pay → replay — is walked with runnable snippets in the [Quickstart](/docs/quickstart), and the discovery surfaces are described in [For agents](/docs/for-agents). Every amount and every accepted rail lives in the gateway's [`/catalog`](https://api.invoket.com/catalog), which is the single source of truth; **no price appears in this article**, and the unit endpoints are listed there too if you would rather orchestrate them yourself. ## What it will not do - **No search by name, no SIRET, no VAT-number entry** on this route. The key is the SIREN. - **No credit opinion, no risk score, no financial statements, no directors.** - **No reconciliation between registers.** Sirene `active` alongside an open BODACC liquidation is reported as exactly that — two sourced facts, not a judgment about which one wins. - **No beneficial ownership.** The control chain is self-declared accounting consolidation, and `data.limits` says so on every response. - **French companies only.** The Sirene identity is the base of the report. One caveat on the capture above: it is a **snapshot-dated illustration**, not a contract. A newly gazetted announcement moves `latest_event`; a VIES retry a minute later may well succeed. Only the derivation rules — provenance per block, undetermined never folded to false, absence always named — are guaranteed. The parameter contract, the full block shapes and the error codes are on the [`GET /company/report` documentation](/docs/api/company-report).