GET /company/events
Returns the official BODACC legal announcements for a French company, looked up by SIREN: insolvency proceedings (safeguard, receivership, judicial liquidation), deregistrations, and business sales, as a dated timeline plus factual flags derived mechanically from it. The answer is served from a local store built from the official BODACC gazette published by DILA (history since 2008, 8.5M+ announcements), so a lookup resolves in milliseconds with no network call and no secret at request time.
Use it as the legal-health pre-action check before an agent invoices,
extends credit to, or onboards a French counterparty - “is this company in
liquidation?”, “has it been deregistered?”, “did it just sell its business?”.
It pairs naturally with
GET /company/resolve: resolve answers who the
company is, events answers what has legally happened to it. See the live
/catalog for the authoritative endpoint listing and
price.
x402 golden rule: the agent pays for the answer to its question. A SIREN
known to the Sirene register with no announcements at all is a successful
answer -> 200 billed, empty list - “no known announcements” is precisely
what a clean counterparty looks like (see the trust
contract below). Requests the service
cannot answer - a malformed SIREN, a failed checksum, an unknown family, or
a well-formed SIREN that does not exist in the Sirene register (see
the one 404) -
leave the 200 range and are not billed.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
siren | string | yes | 9-digit legal-unit identifier (Luhn-checked); its existence is checked in the Sirene register first (unknown -> 404, not billed) |
family | string | no | Filter the view: insolvency, deregistration or sale (default: all) |
GET /company/events?siren=321875205&family=insolvency
siren is validated in two steps: strict form (9 digits), then the Luhn
check digit (no SIREN is exempt - the La Poste SIREN 356000000 passes the
standard check). A well-formed SIREN that fails its checksum is a
400 INVALID_CHECKSUM, distinct from any “not found”: it is almost certainly
a typo, and the agent should fix the input.
Lookup is by SIREN only: BODACC announcements are published per legal
unit, so there is no SIRET input, and no search by name in v1 - the endpoint
serves agents that already hold an identifier (from an invoice, a contract,
a /company/resolve answer).
The family filter changes what the agent looks at, never the facts:
events, total and truncated describe the filtered view, while the
flags are always derived from the company’s
complete timeline. An unknown family value is a 400 INVALID_INPUT.
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "dila-bodacc",
"fetched_at": "2026-07-12T14:50:57Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
provenance.source:dila-bodacc, the served BODACC store.freshness.kind:snapshot;as_ofis the extraction date of the BODACC dataset that backed the answer.
Fields of data
| Field | Type | Description |
|---|---|---|
siren | string | The queried SIREN, echoed |
company | object | Minimal Sirene identity echo: { name, natural_person { last_name, first_name }, status }; null only when the Sirene stock itself could not be read - see coverage.identity (a SIREN unknown to the register is a 404, not an echo-less 200) |
events | array | Announcements, most recent first, in the view selected by family= |
total | int | Honest count of announcements in the view, before the payload cap |
truncated | bool | true when the view was capped to the 100 most recent announcements |
flags | object | Factual flags derived from the complete timeline - see below |
coverage | object | window { from, to } - the ingested history window; identity ("sirene_dataset_unavailable") when the Sirene stock could not be consulted; insolvency_flag when the insolvency flag could not be determined |
Fields of an event
| Field | Type | Description |
|---|---|---|
family | string | insolvency (collective proceedings: safeguard, receivership, liquidation), deregistration (removals from the register) or sale (business sales and transfers) |
nature | string | Original DILA label (e.g. “Jugement de conversion en liquidation judiciaire”); null when the publication does not state one - never invented |
effective_on | string | Judgment/effect date (YYYY-MM-DD) when distinct from publication; null otherwise |
published_on | string | BODACC publication date (YYYY-MM-DD) |
gazette_issue | string | BODACC gazette issue carrying the announcement |
notice_number | int | Notice number within the gazette issue |
court | string | Court or registry that issued the announcement; null when not stated |
text | string | Short original announcement text (truncated at ingestion) |
The view is capped to the 100 most recent announcements: when a company
has more, truncated is true and total keeps the honest count before the
cap.
Flags: facts, not a score
The flags block states BODACC facts derived mechanically from the
company’s complete timeline - never the filtered view, and never an
opinion:
| Flag | Type | Derivation |
|---|---|---|
has_open_insolvency_announcement | bool | null | Three states, read on the most recent insolvency announcement that qualifies the state of the proceeding - see below |
is_deregistered | bool | A deregistration has been published in the BODACC |
latest_event | object | { family, date } of the announcement with the most recent effect date across all families; date is the effect date when known, else publication; ties on effect date are broken by the most recent publication; null when the timeline is empty |
has_open_insolvency_announcement is nullable
| Value | Meaning |
|---|---|
true | The most recent qualifying insolvency announcement is an act of a collective proceeding (opening, conversion, extension, plan ordered, modified or resolved) |
false | It is a closure (or an end of proceeding, or a retracted opening judgment) - or the SIREN has no insolvency announcement at all over the window |
null | Announcements of this family exist, but none of them qualifies the state of the proceeding; coverage.insolvency_flag says so ("undetermined_latest_notice") |
true is a fact about the gazette, not a solvency opinion or a risk score -
the service does not rate companies. null is not a failure and not a false
in disguise: it is the service refusing to answer a question the published
labels do not answer. Agents that branch on this flag must treat it as
nullable - if (flags.has_open_insolvency_announcement) silently reads null
as “nothing to report”, which is exactly the reading this third state exists to
prevent.
Two rules make the derivation predictable:
- The classification is an explicit, closed table of DILA labels - one list
of closures, one list of collective acts, and everything else is
undetermined. There is no default rule “anything that is not a closure
is an opening”. The DILA
collectivefamily is a gazette rubric, not a list of proceeding acts: it also carries registry filings, sanctions aimed at the director, and a catch-all label (“Autre jugement prononçant”, 33,748 announcements) that says nothing about the underlying act. - A non-qualifying announcement does not extinguish the previous ones. The
derivation walks back to the most recent announcement that does qualify; the
flag only becomes
nullwhen no announcement in the timeline qualifies. A registry filing published after a judicial liquidation therefore leaves the flag attrue(see the SAN MARINA example below).
What the service deliberately does not do: reclassify the catch-all by
reading the announcement text. Matching prose for “conciliation” or
“homologation” would be a home-made classifier over free text served as an
official fact - the wrong side of the line this service draws. The announcement
is served in full instead, text included, and the agent reads it.
Example - insolvency view (SAN MARINA)
GET /company/events?siren=321875205&family=insolvency, against the
2026-07-26 BODACC snapshot - receivership opened in 2022, converted to
judicial liquidation in 2023:
{
"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 devant le juge-commissaire dans le délai d'un mois à compter de la présente publication."
},
{
"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 SCP J.P Louis & A.Lageat, Mandat Conduit Par Me J.P Louis 30 Rue Cours Lieutaud 13001 Marseille et SAS LES MANDATAIRES mission conduite par Me Vincent DE CARRIERE 50 Rue Sylvabelle 13006 MARSEILLE."
},
{
"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, date de cessation des paiements le 25 août 2022 désignant administrateur SCP Avazeri-Bonetto, Mission Conduite Par Me F.Avazeri 23/29 Rue Haxo 13001 Marseille et SELARL GILLIBERT & ASSOCIES, mission conduite par Me Vincent GILLIBERT 11 Rue Venture 13001 MARSEILLE avec les pouvoirs : assister le débiteur pour tous les actes de gestion, mandataire judiciaire SCP J.P Louis & A.Lageat, Mandat Conduit Par Me J.P Louis 30 Rue…"
}
],
"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-07-26" }
}
},
"provenance": {
"source": "dila-bodacc",
"fetched_at": "2026-07-26T18:43:01Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-26T00:00:00Z" }
}
}
Note how the flags illustrate the derivation rule: they read the complete
timeline, not the requested view. total: 3 counts the requested view, but
this SIREN has 36 announcements in all - and latest_event points at one
of the 33 you cannot see here: a business sale (“Achat d’un établissement
secondaire ou complémentaire par une personne morale”, Aix-en-Provence
registry), gazetted on 2024-02-01. That announcement carries no published
effect date, so date falls back to its publication, exactly as the rule
states. Meanwhile the most recently published announcement of the timeline
(2024-05-19) carries an effect date of 2017-05-22 - a sale gazetted seven
years after it took effect - and is not retained: the BODACC routinely
publishes an announcement, sales especially, long after the fact, which is
precisely the trap this field avoids.
has_open_insolvency_announcement is true, and note why: the most recent
insolvency announcement is a registry filing (“Dépôt de l’état des créances”)
whose label qualifies nothing, so the derivation walks back to the one below
it - the judgment converting the receivership into a judicial liquidation. A
non-qualifying announcement does not extinguish the fact published before it.
These values are what the 2026-07-26 snapshot returns. An example served
from a snapshot ages with the store: a newly gazetted announcement can move
latest_event at any refresh, so read the numbers as an illustration for the
as_of shown - only the derivation rule is a contract.
Example - the flag the service refuses to guess (BNP PARIBAS)
GET /company/events?siren=662042449&family=insolvency, against the same
2026-07-26 BODACC snapshot. One insolvency announcement, and its DILA label is
the catch-all:
{
"data": {
"siren": "662042449",
"company": { "name": "BNP PARIBAS", "natural_person": null, "status": "active" },
"events": [
{
"family": "insolvency",
"nature": "Autre jugement prononçant",
"effective_on": "2026-04-07",
"published_on": "2026-04-16",
"gazette_issue": "20260073",
"notice_number": 2568,
"court": "Greffe du Tribunal de Commerce de Draguignan",
"text": "Jugement d'homologation de l'accord intervenu dans la procédure de conciliation. Le jugement est déposé au greffe où tout intéressé peut en prendre connaissance. ."
}
],
"total": 1,
"truncated": false,
"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-07-26" },
"insolvency_flag": "undetermined_latest_notice"
}
},
"provenance": {
"source": "dila-bodacc",
"fetched_at": "2026-08-01T17:04:52Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-26T00:00:00Z" }
}
}
The announcement is real, it is French BNP Paribas, and it is filed under the gazette’s insolvency rubric - but “Autre jugement prononçant” is the label DILA uses for everything it does not file elsewhere. Reading it as an opening would have declared a systemically important bank in collective proceedings on what the text shows to be the homologation of a conciliation agreement: a preventive, amicable procedure that opens no collective proceeding at all, and arguably a resolution signal rather than a distress one.
So the flag is null, coverage.insolvency_flag names the reason, and the
announcement is still served with its nature, its court, its dates and its
text. The refusal is to over-qualify a fact, never to hide it: everything
needed to read the situation is in the payload, and the agent decides whether
it wants a human to look.
An empty list is a paid answer
The BODACC does not “know” SIRENs - it knows announcements. A SIREN that
exists in the Sirene register and carries no announcement is therefore a
200, billed, with events: [] - never a 404. The absence of announcements
is the answer the agent asked for, and it is only meaningful over a stated
window:
coverage.window { from, to } carries the history actually ingested (since
2008) and is served on every response, empty or not.
A real empty answer, captured against the production store on 2026-07-12 (an active sole trader with no BODACC announcement):
{
"data": {
"siren": "902120096",
"company": {
"name": null,
"natural_person": { "last_name": "DEMAILLY", "first_name": "LOLITA" },
"status": "active"
},
"events": [],
"total": 0,
"truncated": false,
"flags": {
"has_open_insolvency_announcement": false,
"is_deregistered": false,
"latest_event": null
},
"coverage": {
"window": { "from": "2008-01-01", "to": "2026-07-12" }
}
},
"provenance": {
"source": "dila-bodacc",
"fetched_at": "2026-07-12T15:07:29Z",
"freshness": { "kind": "snapshot", "as_of": "2026-07-12T00:00:00Z" }
}
}
The question “does this company have legal announcements?” received its
answer: none known over the 2008-01-01 -> 2026-07-12 window. The flags
are false and latest_event is null by derivation from the empty
timeline, not by default; the Sirene echo (a natural person: name: null,
natural_person filled) confirms the SIREN exists in the register.
”Unknown to the register” is not “known, without announcements”
These are two different answers, and only one of them is billed. The empty list
above says this company exists and the gazette has nothing on it. A SIREN that
was never assigned by INSEE says nothing about any company at all - so it is
a 404 NOT_FOUND, not billed, with the very same message as
GET /company/resolve:
{ "error": "siren not found in the Sirene register", "code": "NOT_FOUND" }
The existence of an identifier is one single truth across the service: one route cannot answer “unknown” while another bills an answer for the same input. The check runs before the timeline is read, so a nonexistent SIREN never reaches the billing path.
Why this matters more here than it looks: nine digits that pass the Luhn check
are not evidence that a company exists, and the failure mode is not exotic.
000000000 passes the checksum (its digits sum to zero) and is the filler value
the BODACC export carries on lines whose SIREN field was empty - which makes it
the third most frequent SIREN of the store, with hundreds of announcements
attached. Billing a 200 for it returned a timeline of announcements that were
individually real but belonged to hundreds of unrelated companies across
different courts, with affirmative flags on top. The same held for 123456782,
the SIREN used as an example in manuals. A misconfigured ERP, a truncated CSV or
an empty spreadsheet cell produces exactly these inputs, and the agent had no
way to tell the aggregate apart from a genuine company’s history.
Two boundaries keep the rule narrow:
- A 404 is never invented. The existence check is the identity lookup this
route already performed, simply moved ahead of the announcements read - it
costs nothing and it adds no new source. If the Sirene stock cannot be
consulted at all, the answer is served exactly as before, with
coverage.identity = "sirene_dataset_unavailable"stating why the identity echo is missing. Ignorance is never turned into a 404. - Existing and clean stays a paid 200. Nothing above narrows the contract of
this route:
902120096(the empty list shown above) still returns a billed200withevents: []and itscoverage.window. What changed is only the input that names no company.
Sirene identity echo
Each answer carries a minimal company echo from the INSEE Sirene
register - legal name (or the natural person’s name for a sole trader) and
active/ceased status - so the agent can cross-read the timeline against the
company’s identity without a second call. For the full identity card
(legal form, NAF activity, workforce, head office), use
GET /company/resolve.
On this route the echo is missing in one case only: when the Sirene stock
itself cannot be consulted. company is then null, coverage.identity says
why ("sirene_dataset_unavailable"), and the BODACC timeline is still served
on its own authority - a missing echo is never an error. A SIREN the
register does not know is the other case entirely, and it never reaches this
point: it is the 404 above.
Freshness
Answers are served from a snapshot of the BODACC dataset, refreshed by
delta; freshness.as_of carries the extraction date of the image that backed
the answer, and coverage.window.to matches it. If a refresh is missed, the
service keeps serving the last good snapshot - honestly dated by as_of -
and never turns staleness into a 5xx.
Errors
Only requests the service cannot answer leave the 200 range - none of them are billed. Note the deliberate absence of a 404 for a SIREN the register knows: no announcements is a 200 (see above). The only business 404 is a SIREN the register does not know - there, what is missing is the input, not the answer.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_INPUT | Malformed SIREN (wrong length, non-digits), or unknown family value (accepted variants listed in the message) |
| 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, family). Not charged. |
| 400 | INVALID_CHECKSUM | Well-formed SIREN that fails its Luhn check - a probable typo the agent should fix |
| 400 | MISSING_PARAMETER | No siren provided |
| 404 | NOT_FOUND | A well-formed siren= absent from the Sirene register (a legal unit never assigned by INSEE - same message as /company/resolve); also unknown routes. Never for a known SIREN without announcements, and never invented when the Sirene stock cannot be consulted |
| 503 | DATA_UNAVAILABLE | The BODACC store has not been ingested yet - there is no last good snapshot to serve |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "invalid checksum for siren `321875206`", "code": "INVALID_CHECKSUM" }
{ "error": "siren not found in the Sirene register", "code": "NOT_FOUND" }
Attribution
Legal-announcement data is derived from the BODACC (Bulletin officiel des annonces civiles et commerciales), published by DILA under the Licence Ouverte / Open Licence (Etalab). The identity echo comes from the Sirene register published by INSEE, under the same licence.
See also
GET /company/resolve- the full official identity card behind the SIREN: legal name, status, legal form, NAF activity, head office.GET /iban/screen- screen a counterparty’s IBAN against the OFAC/EU/UN sanctions lists before paying it.- For agents - discovery surfaces, the live
/catalogand how settlement works.