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.

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.

ParameterTypeRequiredDescription
codestringFR identityFrench text identifier, for example code-civil
articlestringFR identity; optional for EUArticle number within the text, for example 1240
celexstringEU identityCELEX identifier, for example 32016R0679
elistringEU identityELI 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, commonly legifrance-legi for French law or eur-lex-cellar for EU law.
  • freshness.kind: snapshot; as_of is the store dump date that backed the answer.

Fields of data

FieldTypeDescription
codestringEcho of the requested French text identifier (FR requests)
articlestringEcho of the requested article number, when supplied
celexstringEcho of the requested CELEX identifier (EU requests)
elistringEcho of the requested ELI identifier (EU requests)
labelstringHuman label for the article
versionsarrayEvery known version, sorted by date_debut ascending
countnumberNumber of versions, equal to versions.length
coverage_windowobjectPeriod 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[]

FieldTypeDescription
version_idstringStable identifier of the source version
etatstringLEGI / EUR-Lex status carried as stored, never reinterpreted
date_debutstringStart date of this version, inclusive, in YYYY-MM-DD
date_finstring | nullEnd date, exclusive; omitted for the current version
continuitystringWhether 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_tostringArticle 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:

ValueMeaning
same_articleThis version continues the previous one - an ordinary amendment (also the value of a first version, which has no predecessor)
renumberedThe number stopped designating the previous text: do not read the two versions as one article’s history
unknownNot 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.

StatuscodeCase
400INVALID_REFNo usable identity: neither a French code+article nor an EU celex/eli
404UNKNOWN_ARTICLEThe reference is well formed but no matching article exists in the store
404UNKNOWN_ACTA bare EU act identifier is well formed but no matching act exists
500INTERNALInternal 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 /catalog and how settlement works.