GET /legal/diff

Returns what changed in a legal article’s consolidated text between a date A (from) and a date B (to): the two versions in force at each date are resolved from the local store and their texts are diffed, giving you both a structured segment list (agent-friendly, word-granularity) and a unified patch string (git-style, line-granularity). The lookup is served in milliseconds without an account or API key.

This is the hardest legal signal to reconstruct independently: an agent that calls /legal/article twice and tries to diff the texts cannot do so reliably at scale or across corpus updates. Use this endpoint when you need to know what the law says now that it did not say before - due diligence, regulatory watch, compliance delta, amendment tracking.

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 with readable dates returns a diff -> 200, including when nothing changed (changed: false). An unchanged article is a successful answer, not an error. Requests the service cannot answer - malformed references, missing from, unknown articles - leave the 200 range.

Parameters

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
fromstringrequiredStart date YYYY-MM-DD (date A - the older snapshot)
tostringnoEnd date YYYY-MM-DD (date B); defaults to today’s date

Provide a French identity (code + article) or an EU identity (celex or eli, with optional article). from is always required.

GET /legal/diff?code=code-civil&article=1240&from=2000-01-01&to=2026-01-01
GET /legal/diff?celex=32016R0679&article=17&from=2018-01-01

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
fromstringEffective date A, YYYY-MM-DD
tostringEffective date B, YYYY-MM-DD
from_versionobject | nullVersion in force at from; null when no version covered that date
to_versionobject | nullVersion in force at to; null when no version covered that date
changedbooltrue when the two resolved texts differ
diffobjectDiff payload; empty/zeroed when changed: false

from_version and to_version share the same shape:

FieldTypeDescription
version_idstringStable source identifier for this version
etatstringLEGI / EUR-Lex status carried as stored, never reinterpreted
date_debutstringStart date of this version, inclusive, YYYY-MM-DD
date_finstring | nullEnd date, exclusive; omitted for the current version

diff

FieldTypeDescription
segmentsarrayStructured word-level diff: [ { op, text } ]
unifiedstringGit-style unified patch (line-granularity)
addednumberTotal characters added
removednumberTotal characters removed

op in segments[] is one of equal, insert, or delete.

Boundary semantics

When a date falls outside all known versions for an article - before its first recorded version, or after the article has been fully repealed and no version covers that point - the service treats that side as empty text rather than returning a 4xx. The diff then represents an insertion from nothing (“added since”) or a deletion to nothing (“removed since”). The null side is signalled by from_version: null or to_version: null.

This preserves the x402 golden rule: a known article with a valid reference is always a 200, even when one date is uncovered. Only unknown articles and malformed requests return 4xx.

Example - article that changed between two dates

{
  "data": {
    "code": "code-civil",
    "article": "1240",
    "label": "Code civil, art. 1240",
    "from": "2000-01-01",
    "to": "2026-01-01",
    "from_version": {
      "version_id": "v-1804",
      "etat": "abroge",
      "date_debut": "1804-03-21",
      "date_fin": "2016-10-01"
    },
    "to_version": {
      "version_id": "v-courant",
      "etat": "vigueur",
      "date_debut": "2016-10-01"
    },
    "changed": true,
    "diff": {
      "segments": [
        { "op": "equal", "text": "Tout fait quelconque de l'homme, qui cause a autrui un dommage, oblige celui par la faute duquel il est arrive a le reparer." },
        { "op": "delete", "text": " (ancien article 1382)" }
      ],
      "unified": "@@ -1 +1 @@\n-Tout fait quelconque ... a le reparer. (ancien article 1382)\n+Tout fait quelconque ... a le reparer.",
      "added": 0,
      "removed": 22
    }
  },
  "provenance": {
    "source": "legifrance-legi",
    "fetched_at": "2026-06-20T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
  }
}

Example - unchanged article (changed: false)

When from and to resolve to the same version, or the text is identical, the diff is empty and the response is still a 200. “No change” is a successful answer.

{
  "data": {
    "code": "code-civil",
    "article": "1240",
    "label": "Code civil, art. 1240",
    "from": "2020-01-01",
    "to": "2026-01-01",
    "from_version": {
      "version_id": "v-courant",
      "etat": "vigueur",
      "date_debut": "2016-10-01"
    },
    "to_version": {
      "version_id": "v-courant",
      "etat": "vigueur",
      "date_debut": "2016-10-01"
    },
    "changed": false,
    "diff": {
      "segments": [],
      "unified": "",
      "added": 0,
      "removed": 0
    }
  },
  "provenance": {
    "source": "legifrance-legi",
    "fetched_at": "2026-06-20T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
  }
}

Example - from before the first known version (null side)

When from predates the article’s first version, the service treats the from side as empty text. from_version is null and the diff shows a total insertion.

{
  "data": {
    "code": "code-civil",
    "article": "1240",
    "label": "Code civil, art. 1240",
    "from": "1800-01-01",
    "to": "2026-01-01",
    "from_version": null,
    "to_version": {
      "version_id": "v-courant",
      "etat": "vigueur",
      "date_debut": "2016-10-01"
    },
    "changed": true,
    "diff": {
      "segments": [
        { "op": "insert", "text": "Tout fait quelconque de l'homme, qui cause a autrui un dommage, oblige celui par la faute duquel il est arrive a le reparer." }
      ],
      "unified": "@@ -0,0 +1 @@\n+Tout fait quelconque ...",
      "added": 123,
      "removed": 0
    }
  },
  "provenance": {
    "source": "legifrance-legi",
    "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 sources at request time, search by keyword, infer missing versions or interpret the legal significance of changes. The diff is computed purely on the consolidated text as stored; etat is carried as-is from the source store.

provenance.freshness.as_of tells you which dump backed both resolutions. An amendment that postdates the snapshot will not appear until the store is refreshed.

Errors

Only requests the service cannot answer leave the 200 range.

StatuscodeCase
400INVALID_REFNo usable identity: neither French code+article nor EU celex/eli
400INVALID_DATEfrom or to is present but not formatted as YYYY-MM-DD
400MISSING_PARAMfrom is absent
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`+`article` or `celex`/`eli` is required", "code": "INVALID_REF" }
{ "error": "query parameter `from` is required (date A, format YYYY-MM-DD)", "code": "MISSING_PARAM" }
{ "error": "invalid `from` `01/01/2020`; expected ISO calendar format YYYY-MM-DD", "code": "INVALID_DATE" }
{ "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/history - the full version timeline of an article (when it changed, not what changed).
  • 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.