POST /legal/history/batch

Returns version timelines for a list of legal references in a single call: up to 100 references, French or European Union, settled with one x402 payment for the whole batch. Each entry runs through the same local timeline engine as GET /legal/history, so the response is one ordered array of timelines - when each article changed and which versions exist.

This is the portfolio monitoring primitive: an agent re-checks a whole set of articles periodically to detect when any of them gained a new version - regulatory drift across a contract’s citations, amendment tracking over a compliance register, due-diligence sweeps - without issuing one paid call per reference. Like the single endpoint, the payload is deliberately compact: each verdict carries the timeline only, not the consolidated text and not a diff.

The lookup is served from local snapshots of the LEGI and EUR-Lex corpora: no account, no API key and no network fetch to Legifrance or EUR-Lex at request time.

x402 golden rule: the agent pays for the answer to its question. A well-formed batch is a successful answer -> 200, even when some references are repealed or not covered at all. Those cases are reported per item, by the version etat or by coverage.complete: false - never as a batch error. The 4xx range is reserved for requests the service cannot answer as a batch: missing or malformed body, empty list, batch over the cap or a malformed item.

Request

POST with a JSON body. Set Content-Type: application/json.

POST /legal/history/batch
Content-Type: application/json
{
  "refs": [
    { "code": "code-civil", "article": "1240" },
    { "celex": "32016R0679", "article": "17" }
  ]
}
FieldTypeRequiredDescription
refsobject[]yesLegal references to resolve, 1 to 100 entries

There is no date field on a reference: history always returns the whole timeline. Each item is either a French reference or an EU reference:

FieldTypeRequiredDescription
codestringFRFrench text identifier, for example code-civil
articlestringFR onlyArticle number for code; optional for EU act-level lookups
celexstringEU idCELEX identifier, for example 32016R0679
elistringEU idELI identifier, used when celex is absent

For French references, provide code and article. For EU references, provide at least one of celex or eli; article is optional when targeting the act head. If an item contains a non-empty code, it is treated as French.

Volume pricing

This endpoint uses per-unit pricing: one x402 settlement for the request, with the payable amount derived from the number of references in refs - a base plus a per-reference unit, capped at 100 references. The value of the call grows with the size of the portfolio you monitor. The gateway counts the array length; it does not inspect legal content to compute price.

The live /catalog is the price authority and exposes the current base, unit, maximum units, accepted assets and networks. This page describes the pricing model, not the amounts: do not cache figures from here.

200 response - UnifiedResponse

{
  "data": {
    "count": 2,
    "results": [ { ... }, { ... } ]
  },
  "provenance": {
    "source": "legifrance-legi + eur-lex-cellar",
    "fetched_at": "2026-06-20T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
  }
}
  • count: number of submitted references, equal to results.length.
  • results: one verdict per input item, in the same order as the input. Covered items have the same field shape as the data of GET /legal/history: echoed identifiers, label, versions[] and count.
  • Unknown references remain item-level verdicts in the 200 response. They echo the resolvable identifiers, set coverage.complete to false with a reason, and omit fields that cannot be served such as label, versions and count.
  • A single provenance block covers the whole batch. When the batch touches both legal corpora, provenance.source names both; freshness.kind remains snapshot and as_of is the store dump date that backed the answer.

See GET /legal/history for the full versions[] field table, the etat status semantics and the attribution rules.

Example - mixed French and EU batch

{
  "data": {
    "count": 2,
    "results": [
      {
        "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,
        "coverage": { "complete": true }
      },
      {
        "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,
        "coverage": { "complete": true }
      }
    ]
  },
  "provenance": {
    "source": "legifrance-legi + eur-lex-cellar",
    "fetched_at": "2026-06-20T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
  }
}

A version whose most recent etat is abroge is still part of a successful item verdict: the repeal lives in the timeline, never converted into an HTTP error.

Example - unknown reference is still a 200 item verdict

In a well-formed batch, an unknown article does not fail the whole request. It is returned as an incomplete verdict that echoes the identity and explains the gap.

{
  "data": {
    "count": 1,
    "results": [
      {
        "code": "code-civil",
        "article": "9999",
        "coverage": {
          "complete": false,
          "reason": "`code-civil-art-9999` is not covered by the store"
        }
      }
    ]
  },
  "provenance": {
    "source": "legifrance-legi",
    "fetched_at": "2026-06-20T12:00:00Z",
    "freshness": { "kind": "snapshot", "as_of": "2026-06-01T00:00:00Z" }
  }
}

Errors

Only requests the service cannot answer leave the 200 range. The cap is checked before any resolution, so an oversized batch is rejected without being billed an answer.

StatuscodeCase
400INVALID_BODYBody missing or not JSON, not an object, or refs missing
400EMPTY_BATCHrefs is an empty array ([])
400BATCH_TOO_LARGEMore than 100 references in a single call
400INVALID_REFAn item has no usable identity (neither FR code+article nor EU celex/eli); error names refs[i]
500INTERNALInternal error (detail logged, not exposed)
{ "error": "request body must be a JSON object with a `refs` array", "code": "INVALID_BODY" }
{ "error": "batch `refs` must contain at least one reference", "code": "EMPTY_BATCH" }
{ "error": "batch `refs` exceeds the maximum of 100 references", "code": "BATCH_TOO_LARGE" }
{ "error": "refs[1]: reference must provide `code`+`article` (FR) or `celex`/`eli` (EU)", "code": "INVALID_REF" }

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 single-reference version timeline, with the full field tables and status semantics.
  • POST /legal/diff/batch - what changed between two dates across a list of references, in one settlement.
  • For agents - discovery surfaces, the live /catalog and how settlement works.