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.
- For the full consolidated text at a date, call
GET /legal/articleorGET /legal/eu-act. - For the list of all versions (without text or diff), call
GET /legal/history.
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
| 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 |
from | string | required | Start date YYYY-MM-DD (date A - the older snapshot) |
to | string | no | End 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, 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 |
from | string | Effective date A, YYYY-MM-DD |
to | string | Effective date B, YYYY-MM-DD |
from_version | object | null | Version in force at from; null when no version covered that date |
to_version | object | null | Version in force at to; null when no version covered that date |
changed | bool | true when the two resolved texts differ |
diff | object | Diff payload; empty/zeroed when changed: false |
from_version and to_version share the same shape:
| Field | Type | Description |
|---|---|---|
version_id | string | Stable source identifier for this version |
etat | string | LEGI / EUR-Lex status carried as stored, never reinterpreted |
date_debut | string | Start date of this version, inclusive, YYYY-MM-DD |
date_fin | string | null | End date, exclusive; omitted for the current version |
diff
| Field | Type | Description |
|---|---|---|
segments | array | Structured word-level diff: [ { op, text } ] |
unified | string | Git-style unified patch (line-granularity) |
added | number | Total characters added |
removed | number | Total 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.
| Status | code | Case |
|---|---|---|
| 400 | INVALID_REF | No usable identity: neither French code+article nor EU celex/eli |
| 400 | INVALID_DATE | from or to is present but not formatted as YYYY-MM-DD |
| 400 | MISSING_PARAM | from is absent |
| 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`+`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
/catalogand how settlement works.