Check a French company for insolvency: its BODACC timeline
One paid call returns a French company's BODACC timeline — insolvency judgments, deregistrations, business sales — with factual flags, and a flag it refuses to guess.
By Matthias Begot · · Updated
An agent about to invoice a customer on 30-day terms, release a first order or
onboard a supplier is making a credit decision. The question it can actually
answer from an official source is narrow and useful: has anything been
published in the French legal gazette about this company?
GET /company/events answers it by SIREN — a dated
timeline of insolvency judgments, deregistrations and business sales drawn from
the BODACC, plus three flags derived mechanically from that timeline. No
account, no key, no network call at request time.
Two properties decide whether an integration reads it correctly. An empty list is a paid answer — the most common one, in fact — and the insolvency flag is nullable, because when the published label does not qualify what happened, the service says so instead of guessing. Everything below was captured against production on 11 August 2026, against the BODACC snapshot dated 2026-08-09.
The volume behind the question is not theoretical: INSEE, republishing Banque de France data, counted 6,139 business failures in May 2026 alone (Insee, Défaillances d’entreprises, published 21 July 2026). Roughly six thousand counterparties a month stop being counterparties.
The timeline, captured
GET /company/events?siren=321875205&family=insolvency — a retail chain whose
receivership was converted into judicial liquidation:
{
"data": {
"siren": "321875205",
"company": { "name": "SAN MARINA", "natural_person": null, "status": "active" },
"events": [
{
"family": "insolvency",
"nature": "Dépôt de l'état des créances",
"effective_on": "2023-08-25",
"published_on": "2023-09-05",
"gazette_issue": "20230170",
"notice_number": 2594,
"court": "Greffe du Tribunal de Commerce de Marseille",
"text": "L'état des créances est déposé au greffe où tout intéressé peut présenter réclamation…"
},
{
"family": "insolvency",
"nature": "Jugement de conversion en liquidation judiciaire",
"effective_on": "2023-02-20",
"published_on": "2023-02-23",
"gazette_issue": "20230038",
"notice_number": 1758,
"court": "Greffe du Tribunal de Commerce de Marseille",
"text": "Jugement prononçant la liquidation judiciaire désignant liquidateur…"
},
{
"family": "insolvency",
"nature": "Jugement d'ouverture d'une procédure de redressement judiciaire",
"effective_on": "2022-09-22",
"published_on": "2022-09-27",
"gazette_issue": "20220187",
"notice_number": 3494,
"court": "Greffe du Tribunal de Commerce de Marseille",
"text": "Jugement prononçant l'ouverture d'une procédure de redressement judiciaire…"
}
],
"total": 3,
"truncated": false,
"flags": {
"has_open_insolvency_announcement": true,
"is_deregistered": false,
"latest_event": { "family": "sale", "date": "2024-02-01" }
},
"coverage": { "window": { "from": "2008-01-01", "to": "2026-08-09" } }
},
"provenance": {
"source": "dila-bodacc",
"fetched_at": "2026-08-11T15:31:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-08-09T00:00:00Z" }
}
}
Every announcement carries its gazette issue, notice number and court — the coordinates a human can use to pull the original notice. The endpoint serves the published fact, not a summary of it.
Note the relationship between the view and the flags. family=insolvency
narrowed events to three, and total: 3 honestly counts that view — but
this SIREN has 36 announcements in full, and latest_event points at one
of the 33 not shown: a business sale gazetted in February 2024. The filter
changes what the agent looks at, never the facts the flags are derived from.
An agent that filters and then reads flags is reading the complete timeline
either way.
Flags are facts, not a score
Three flags, each a mechanical derivation over the complete timeline:
| Flag | What it states |
|---|---|
has_open_insolvency_announcement | true / false / null — read on the most recent insolvency announcement that qualifies the state of the proceeding |
is_deregistered | A deregistration has been published in the gazette |
latest_event | { family, date } of the announcement with the most recent effect date, across all families; null when the timeline is empty |
true is a fact about the gazette, not a solvency opinion. There is no score,
no rating, no probability of default — those would be an inference wearing an
official register’s authority, which is the line this endpoint does not cross.
The third state exists because the labels do not always answer
GET /company/events?siren=662042449&family=insolvency returns one
announcement, filed under the gazette’s collective-proceedings rubric:
"events": [
{
"family": "insolvency",
"nature": "Autre jugement prononçant",
"effective_on": "2026-04-07",
"published_on": "2026-04-16",
"court": "Greffe du Tribunal de Commerce de Draguignan",
"text": "Jugement d'homologation de l'accord intervenu dans la procédure de conciliation…"
}
],
"flags": {
"has_open_insolvency_announcement": null,
"is_deregistered": false,
"latest_event": { "family": "insolvency", "date": "2026-04-07" }
},
"coverage": {
"window": { "from": "2008-01-01", "to": "2026-08-09" },
"insolvency_flag": "undetermined_latest_notice"
}
The SIREN is 662042449 — BNP Paribas. Under a default rule of “anything filed
under the insolvency rubric that is not a closure is an opening”, a
systemically important bank would have been flagged as being in collective
proceedings. What the text actually shows is the homologation of a
conciliation agreement: a preventive, amicable procedure that opens no
collective proceeding, and arguably a resolution signal rather than a distress
one.
So the derivation runs off an explicit, closed table of gazette labels —
one list of closures, one list of collective acts, everything else
undetermined — and the flag comes back null with
coverage.insolvency_flag naming the reason. "Autre jugement prononçant" is
the catch-all DILA files under when nothing else fits.
Two consequences for the code that consumes this:
if (flags.has_open_insolvency_announcement)readsnullas “nothing to report” — precisely the reading the third state exists to prevent. Branch on the three states explicitly.- A non-qualifying announcement does not extinguish the ones before it. In
the SAN MARINA capture, the most recent insolvency notice is a registry filing
that qualifies nothing; the derivation walks back to the conversion judgment
underneath, and the flag stays
true.
The refusal is to over-qualify, never to hide: the announcement is served in
full — nature, court, dates, text — and the agent decides whether a human
should look.
The most common answer is an empty list, and it is billed
To measure what an agent actually gets, we queried 1,200 random checksum-valid SIRENs against the production store on 11 August 2026:
| Outcome | Count |
|---|---|
404 — SIREN unknown to the Sirene register (not billed) | 796 |
200 — legal unit exists, no announcement over the window | 331 |
200 — legal unit exists, at least one announcement | 73 |
So of the 404 real legal units, 82 % had a completely clean gazette record.
That is the answer the agent asked for, and it is only meaningful because
coverage.window { from, to } is served on every response, empty or not:
“nothing known between 2008-01-01 and 2026-08-09” is a statement; “nothing
found” is not.
Among the 73 with a record, the families were 61 deregistrations, 33
insolvency announcements and 23 sales; 59 companies carried
is_deregistered: true and 3 an open insolvency announcement. Deregistration —
the counterparty no longer being on the register at all — is the single most
frequent thing the gazette will tell you about a random French company. One of
them, 811791888:
{
"company": { "name": null, "natural_person": null, "status": "ceased" },
"events": [
{ "family": "deregistration", "nature": "Radiation", "effective_on": "2024-02-27", "published_on": "2024-03-05", "court": "Greffe du Tribunal de Commerce de Montpellier", "text": null },
{ "family": "deregistration", "nature": "Radiation", "effective_on": "2023-06-21", "published_on": "2023-06-25", "court": "Greffe du Tribunal de Commerce de Montpellier", "text": null },
{ "family": "deregistration", "nature": null, "effective_on": null, "published_on": "2018-07-25", "court": "GREFFE DU TRIBUNAL DE COMMERCE DE MONTPELLIER", "text": null }
],
"total": 3,
"flags": { "has_open_insolvency_announcement": false, "is_deregistered": true, "latest_event": { "family": "deregistration", "date": "2024-02-27" } }
}
Two things to read here. The Sirene echo says status: "ceased", agreeing with
the gazette — a cross-check available in the same payload, without a second
call. And the 2018 announcement carries nature: null and effective_on: null, because the publication itself states neither. Across the 117
announcements in the sample, 49 carried no nature at all. A null there is
the gazette’s silence, faithfully transmitted; the alternative — inferring a
label from the surrounding fields — would be a home-made classifier served as
an official fact.
Large timelines are capped, honestly. GET /company/events?siren=652014051
(Carrefour) returns total: 117 with truncated: true: the view is limited to
the 100 most recent announcements, and total keeps the count from before
the cap rather than reporting what fits in the payload.
”Unknown to the register” is not “known, without announcements”
These are different answers, and only one of them is billed. A well-formed
SIREN that INSEE never assigned is a 404 NOT_FOUND, unbilled — the
existence check runs against Sirene before the timeline is read, so a
nonexistent key never reaches the billing path:
GET /company/events?siren=000000000 → 404 { "code": "NOT_FOUND", "error": "siren not found in the Sirene register" }
GET /company/events?siren=123456782 → 404 { "code": "NOT_FOUND", … }
GET /company/events?siren=321875206 → 400 { "code": "INVALID_CHECKSUM", "error": "siren failed its checksum (Luhn): likely a typo" }
The check matters more than it looks. 000000000 passes the Luhn test — its
digits sum to zero — and it is the filler the BODACC export carries on lines
whose SIREN field was empty, which makes it one of the most frequent “SIRENs”
in the raw gazette, with hundreds of unrelated announcements attached to it. A
misconfigured ERP, a truncated CSV or an empty spreadsheet cell produces
exactly that input. Billing a timeline for it would return announcements that
are individually real and collectively meaningless, with affirmative flags on
top.
123456782 — the SIREN used as an example in manuals — is the same story. And
the boundary stays narrow in the other direction: if the Sirene stock itself
cannot be consulted, the timeline is served anyway with
coverage.identity: "sirene_dataset_unavailable". Ignorance is never turned
into a 404.
The blind spot to plan for: the gazette publishes late
This is the honest limit of the endpoint, and it is measurable. Across the sampled announcements that carry both dates, the gap between the effect date and the publication date had a median of 16 days, a 90th percentile of 79 days, and a maximum of 777 days.
An announcement enters the store only once DILA has published it. So “no
insolvency announcement today” means nothing has been gazetted yet — a
judgment handed down two weeks ago will typically not be there. For a credit
decision, that is a bounded, declared blind spot rather than a hidden one, and
it is the reason effective_on and published_on are two separate fields
instead of one convenient “date”.
The same lag is why latest_event ranks by effect date and not by
publication: in the SAN MARINA timeline, the most recently published
announcement (May 2024) carries an effect date of May 2017 — a sale
gazetted seven years after the fact. Ranking by publication would have made a
2017 event look like the company’s latest news.
Three more scope limits worth stating plainly:
- No score, no prediction. The flags are derived facts. Nothing here rates a company or forecasts a default.
- BODACC only, by SIREN. No search by name, no SIRET input — announcements are published per legal unit. The history ingested starts in 2008.
- A snapshot, honestly dated.
freshness.kindissnapshotandas_ofcarries the extraction date. A missed refresh means the last good snapshot keeps being served under its true date — staleness is never turned into a 5xx.
Wiring it up
- Branch on three states, not two.
true,falseandnullforhas_open_insolvency_announcement. Treatnullas “a human reads thetext”, never as “clear”. - Read
is_deregisteredfirst for onboarding. It is the most frequent finding, and a deregistered counterparty is not one you invoice. - Store
provenance.freshness.as_ofwith the verdict. “On what basis did the agent extend credit on 11 August 2026” is answerable only if the snapshot date sits beside the decision. - Keep the two dates apart. Age your decision on
effective_on, plan for the publication lag onpublished_on. - Distinguish 400 from 404. A checksum failure means fix the input; a
NOT_FOUNDmeans this key names no company — stop, do not retry. - Discover, then pay. The
402→ pay → replay cycle is walked with runnable snippets in the Quickstart, and the machine discovery surfaces are described in For agents.
Where it sits in a counterparty check
The gazette answers what has legally happened. It does not answer who this
is — that is /company/resolve, whose Sirene
identity echo is already embedded in every events response — nor can I invoice
them across borders (/company/vat), can they
receive the e-invoice (/company/peppol), or who
controls them (/company/ownership). If the
agent wants all of it in a single settlement,
GET /company/report bundles the blocks by SIREN
with provenance per block, and
/preflight/supplier turns that bundle into a
one-call onboarding verdict.
Legal health is also not sanctions screening: that is
/screen/entity for a name and
/iban/screen for the account you are about to pay. A
SIREN read off an inbound invoice comes from
/invoice/read.
Every amount and every accepted settlement rail lives in the gateway’s
/catalog, which is the single source of
truth — no price appears in this article.
Announcement data is derived from the BODACC, published by DILA under the Licence Ouverte / Open Licence (Etalab 2.0); the identity echo comes from the INSEE Sirene register under the same licence.
The captures and the sample above are snapshot-dated illustrations, not a
contract: a new gazette issue moves latest_event at any refresh. Only the
rules are guaranteed — one SIREN per call, an empty timeline served as a paid
answer over a stated window, a flag left null rather than guessed, and a key
the register has never seen never billed. The full parameter and field contract
is on the GET /company/events
documentation.