GET /legal/history
Returns the version timeline of a legal article, French or
European Union: each stored version with its version_id, its
stored etat status and its validity dates. The timeline is served from a
local store built from the official LEGI and EUR-Lex datasets, so a covered
reference resolves in milliseconds without an account or API key. The corpus
currently covers a selected set of French codes and EU acts and is
expanding; an uncovered reference returns 404, never a paid empty timeline.
This is the monitoring primitive: an agent re-checks an article periodically to detect when a new version has appeared - regulatory drift, amendment tracking, compliance monitoring - without re-reading full text every time. The payload is deliberately compact: it answers when an article changed and which versions exist, not what the text says or what changed between two versions.
- For the full consolidated text of a version, call
GET /legal/article(orGET /legal/eu-act) withdate=. - For what changed between two dates, call
GET /legal/diff.
See the live /catalog for the authoritative endpoint
listing and price.
x402 golden rule: the agent pays for the answer to its question. A well
formed, known article returns its timeline -> 200, including when the most
recent version is abroge: the timeline is the data being sold. Requests the
service cannot answer - malformed references, unknown articles, unknown acts -
leave the 200 range.
Parameters
A single endpoint covers both corpora. Supply a French identity or a
European Union identity. There is no date parameter - history always
returns the whole timeline.
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | FR identity | French text identifier, for example code-civil |
article | string | FR identity; optional for EU | Article number within the text, for example 1240 |
celex | string | EU identity | CELEX identifier, for example 32016R0679 |
eli | string | EU identity | ELI identifier; used when celex is absent |
For a French article, provide code + article. For an EU article, provide
celex (or eli) and optionally article; omitting article targets the act
head.
GET /legal/history?code=code-civil&article=1240
GET /legal/history?celex=32016R0679&article=17
200 response - UnifiedResponse
{
"data": { ... },
"provenance": {
"source": "legifrance-legi",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
}
}
provenance.source: source identifier from the served store, commonlylegifrance-legifor French law oreur-lex-cellarfor EU law.freshness.kind:snapshot;as_ofis the store dump date that backed the answer.
Fields of data
| Field | Type | Description |
|---|---|---|
code | string | Echo of the requested French text identifier (FR requests) |
article | string | Echo of the requested article number, when supplied |
celex | string | Echo of the requested CELEX identifier (EU requests) |
eli | string | Echo of the requested ELI identifier (EU requests) |
label | string | Human label for the article |
versions | array | Every known version, sorted by date_debut ascending |
count | number | Number of versions, equal to versions.length |
coverage_window | object | Period over which the text or act is actually queryable: from (inclusive), to (exclusive, null when open) |
The identity fields are echoed exactly as supplied: code/article for a
French request, celex/eli/article for an EU request. There is no
text and no diff in the payload - timeline only, by design.
versions[]
| Field | Type | Description |
|---|---|---|
version_id | string | Stable identifier of the source version |
etat | string | LEGI / EUR-Lex status carried as stored, never reinterpreted |
date_debut | string | Start date of this version, inclusive, in YYYY-MM-DD |
date_fin | string | null | End date, exclusive; omitted for the current version |
continuity | string | Whether this version continues the previous one under the same article number: same_article, renumbered or unknown. French law only - omitted for EU law |
renumbered_from / renumbered_to | string | Article number the text came from / went to, when the source filiation records it; omitted otherwise - never guessed |
Versions are sorted by date_debut. etat is carried as stored and may
include vigueur, abroge, abroge_diff, vigueur_diff, modifie and
perime. A version with no date_fin is the open-ended current version.
Renumbering: two different articles under one number
A reform can make an article number stop designating one text and start designating another. The timeline then looks like an ordinary amendment while it is nothing of the sort - the most misleading signal a monitoring agent can get.
The textbook case is French Civil Code article 1240. Until 2016-10-01 that number carried “payment made in good faith to the holder of the claim is valid”. The 2016 reform of the law of obligations renumbered the tort provisions: the former article 1382 (“any act of man which causes damage to another obliges him to repair it”) became 1240, and the old 1240 is gone. Two unrelated texts, one number, one timeline.
continuity marks the version that breaks the number:
| Value | Meaning |
|---|---|
same_article | This version continues the previous one - an ordinary amendment (also the value of a first version, which has no predecessor) |
renumbered | The number stopped designating the previous text: do not read the two versions as one article’s history |
unknown | Not determinable (too little text to compare) - neither asserted nor denied |
Nothing is rewritten, merged or hidden: every version is still served exactly as
recorded, with its own etat, dates and text. The marker is added next to
the data, never in its place. The detection uses the source filiation first (a
version whose text was formally transferred to another number) and, failing
that, a textual heuristic - which is why the matching flag on
GET /legal/diff is named renumbering_suspected. EU
law is out of scope: consolidations keep their numbering, so the field is absent
there.
Example - French article with two versions
Article 1240 of the Civil Code is also the renumbering case described above: the
second version carries continuity: "renumbered" because the number changed
meaning in 2016.
{
"data": {
"code": "code-civil",
"article": "1240",
"label": "Code civil, art. 1240",
"versions": [
{
"version_id": "LEGIARTI000006437044",
"etat": "modifie",
"date_debut": "1804-03-21",
"date_fin": "2016-10-01",
"continuity": "same_article"
},
{
"version_id": "LEGIARTI000032041571",
"etat": "vigueur",
"date_debut": "2016-10-01",
"continuity": "renumbered"
}
],
"count": 2,
"coverage_window": { "from": "1804-03-21", "to": null }
},
"provenance": {
"source": "legifrance-legi",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
}
}
Example - a repealed article still returns its timeline
When the most recent version is abroge, the article is still a successful
answer: the repeal is part of the timeline, not an error. The status lives in
each version’s etat; it is never converted into an HTTP error.
{
"data": {
"code": "code-test",
"article": "abroge",
"label": "Code test, art. abroge",
"versions": [
{
"version_id": "v-1",
"etat": "vigueur",
"date_debut": "2010-01-01",
"date_fin": "2020-01-01"
},
{
"version_id": "v-2",
"etat": "abroge",
"date_debut": "2020-01-01"
}
],
"count": 2
},
"provenance": {
"source": "legifrance-legi",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
}
}
Example - EU article
{
"data": {
"celex": "32016R0679",
"article": "17",
"label": "Regulation (EU) 2016/679 (GDPR), Article 17",
"versions": [
{
"version_id": "32016R0679-art-17-v1",
"etat": "vigueur",
"date_debut": "2018-05-25"
}
],
"count": 1
},
"provenance": {
"source": "eur-lex-cellar",
"fetched_at": "2026-06-20T12:00:00Z",
"freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
}
}
Coverage honesty
The service answers from a local snapshot of the LEGI and EUR-Lex corpora. It
does not fetch Legifrance or EUR-Lex at request time, search by keyword, infer
missing versions or reinterpret statuses. The versions array reflects exactly
what the served store dump contains; a newer amendment that postdates the
snapshot appears only after the store is refreshed (provenance.freshness.as_of
tells you which dump backed the answer).
etat is carried from the source store as legal metadata; callers should treat
each value as the authoritative status for that version. Unknown articles and
unknown acts are not partial paid answers: they return 4xx errors.
How far back can you go
coverage_window answers that in the same call as the timeline. A timeline does
not tell you on its own: an article may hold a single version while its text or
act is covered years earlier, and historical depth varies per text (the
e-Commerce Directive, in force since 2000, is only covered from 2024-02-17).
Dates outside the window are refused by
/legal/article and
/legal/eu-act with OUT_OF_COVERAGE_WINDOW (a 4xx,
not billed) — reading the window first is how a monitoring agent avoids paying
for a call that cannot succeed.
Errors
Only requests the service cannot answer leave the 200 range.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_REF | No usable identity: neither a French code+article nor an EU celex/eli |
| 404 | UNKNOWN_ARTICLE | The reference is well formed but no matching article exists in the store |
| 404 | UNKNOWN_ACT | A bare EU act identifier is well formed but no matching act exists |
| 500 | INTERNAL | Internal error (detail logged, not exposed) |
{ "error": "query parameter `code` (e.g. `code-civil`) with `article`, or `celex`/`eli`, is required", "code": "INVALID_REF" }
{ "error": "no article `9999` found for text `code-civil` in the store", "code": "UNKNOWN_ARTICLE" }
{ "error": "no act `39999R9999` found in the store", "code": "UNKNOWN_ACT" }
Attribution
French legislation and regulation data is derived from the LEGI dataset made available by the Direction de l’information legale et administrative (DILA) on Legifrance, under the Licence Ouverte / Etalab open licence. EU law data is derived from EUR-Lex / Cellar data made available by the Publications Office of the European Union; reuse must preserve attribution to the European Union and EUR-Lex.
See also
GET /legal/diff- what changed in an article’s text between two dates.GET /legal/article- full consolidated text of a French article at a date.GET /legal/eu-act- full consolidated text of an EU act or article at a date.- For agents - discovery surfaces, the live
/catalogand how settlement works.