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

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

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.

Example - French article with two versions

{
  "data": {
    "code": "code-civil",
    "article": "1240",
    "label": "Code civil, art. 1240",
    "versions": [
      {
        "version_id": "v-1804",
        "etat": "abroge",
        "date_debut": "1804-03-21",
        "date_fin": "2016-10-01"
      },
      {
        "version_id": "v-courant",
        "etat": "vigueur",
        "date_debut": "2016-10-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 - 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.

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.