emendrixAccount

About this site

The JSON API

The record this site renders, as files a program can fetch. There is no query endpoint, no account and no key: every answer is a file, and the record's files are the committed bytes of the changelogs repository, at the same relative paths as in a clone of it.

Three ways in

/api/v1/substances.json lists every CAS and EC number an entry in the record prints, and /api/v1/substances/<scheme>-<value>.json gives each one's entries and changes as data.

Layout

PathWhat it isFrom
/api/this page, with the CI example at its endthe site build
/api/v1/index.jsonthe root index: one row per actthe record, served as committed
/api/v1/<act_dir>/index.jsonone act's versions, and every change per provisionthe record
/api/v1/<act_dir>/changes/<version>.jsonone version's payload, with the textsthe record
/api/v1/catalogue.jsonlabels, aliases, sectors, page and feed addressesthe site build
/api/v1/schema/<name>.schema.jsonthe JSON Schema of each documentthe site build
/api/v1/substances.jsonevery CAS and EC number an entry printsthe site build
/api/v1/substances/<scheme>-<value>.jsonone number's entries and changesthe site build

<act_dir> is the act's corpus and key, and <version> the version it is the payload of. Every path an index names is relative to /api/v1/. The schemas: act-index, change, entry, index, register, twins, catalogue, substance. Each carries its own file name as a relative $id.

The three dates

updated_on on a version's row is the latest of detected_on and every repair's date, the field to sort by to see what moved since you last looked.

Where the sources differ: signals and reasons

Three signals look at every transition: structural_diff, corpus_metadata and instruction_parse. Each reports one status per change:

A change is disputed when one signal observed it and another is absent, or when the signals that observed it name kinds of change that share nothing. Each such change carries a dispute_reason, every other change carries null, and every row of an act index carries the field whether or not an older payload stored it. Filter on the reason or on the signal triple, never on disputed alone. The seven reasons, in the order they are tried:

A reason is a reading of stored verdicts and says nothing about the law. The methodology page explains each signal and how often the sources differ.

Units no change carries

Corrections

The record is corrected forward and its history is never rewritten. A repaired version gets a new sha256, a later updated_on and one more repairs row; a change a repair withdraws disappears from provisions. Key cached rows on (act, version, location, occurrence) and re-read a version whenever its sha256 moves.

Polling, CORS and keys

Poll the root index, never the whole tree. An act's index_sha256 moves exactly when its act index's bytes move, and a version's sha256 exactly when its payload's do, so a consumer holding a file whose hash still matches has nothing to fetch.

Any origin may GET any file. There are no credentials, no keys and no cookies, and no rate promise: a client that polls more often than the record is written gets the same bytes and gains nothing.

Licence

The record is licensed as its changelogs repository states, which also says on what terms the legislative text it quotes is reused.

Examples

Every act, with its newest version, its counts and its title:

curl -fsSL https://emendrix.eu/api/v1/index.json \
  | jq -r '.acts[] | [.key, .newest_version, .changes, .disputed, .title] | @tsv'

The newest change to AR 1 of 32006R1907:

curl -fsSL https://emendrix.eu/api/v1/eu/32006R1907/index.json \
  | jq '.provisions["AR 1"][0]'

The MCP server

The same record, for a model: a read-only MCP server at /mcp, answering streamable HTTP with one JSON response per request, with no session, no key and no account. Its eight tools are list_acts, find_provisions, changes_since, provision_history, get_change, get_event, list_disputed and find_substance, and its four resources emendrix://acts, emendrix://acts/{act}, emendrix://changes/{act}/{version}/{location} and emendrix://methodology. Every result carries the disclaimer and the file each fact was read from, with that file's sha256.

It decides nothing: it does not read or interpret law, cannot search law text and cannot resolve a free reference such as Art. 50(2). Name an act by its key and a provision by its canonical location, as list_acts and find_provisions give them.

The server describes itself at /mcp/server-card, and the site's /.well-known/api-catalog names this endpoint and the JSON API.

A client that takes a URL needs only https://emendrix.eu/mcp. In Claude Code:

claude mcp add --transport http emendrix https://emendrix.eu/mcp

In a client configured by a JSON file:

{
  "mcpServers": {
    "emendrix": {
      "type": "http",
      "url": "https://emendrix.eu/mcp"
    }
  }
}

Watch a provision from CI

A GitHub Actions workflow that opens an issue when a provision you pin has a newer change. It is an example to adapt rather than a maintained action: set PIN to the act and the canonical location, and SEEN to the version you have already reviewed. It needs no secret beyond the job's own token and dedupes by issue title. It uses jq and gh, which GitHub-hosted runners have preinstalled.

# .github/workflows/regulations.yml: opens an issue when a provision you pin changes.
on:
  schedule: [{ cron: "17 6 * * 1" }]
  workflow_dispatch: {}
permissions: { issues: write }
jobs:
  check:
    runs-on: ubuntu-latest
    env:
      PIN: "32017R0745 AN I"
      SEEN: "02017R0745-20260101"
      GH_TOKEN: ${{ github.token }}
      GH_REPO: ${{ github.repository }}
    steps:
      - run: |
          act=${PIN%% *}; loc=${PIN#* }
          slug=$(printf '%s' "$loc" | tr 'A-Z ' 'a-z-' | tr -d '()')
          curl -fsSL -A "emendrix-snippet/1" "https://emendrix.eu/api/v1/eu/$act/index.json" -o idx.json
          new=$(jq -r --arg l "$loc" '.provisions[$l][0].version // empty' idx.json)
          if [ -z "$new" ] || [ "$new" = "$SEEN" ]; then exit 0; fi
          title="$act $loc changed: $new"
          gh issue list --state all --search "\"$title\" in:title" --json title -q '.[].title' \
            | grep -qxF "$title" && exit 0
          row=$(jq -c --arg l "$loc" '.provisions[$l][0]' idx.json)
          gh issue create --title "$title" --body "$(printf '%s\n\n%s\n\n%s' \
            "Newest index row: $row" \
            "History, verbatim text and citations: https://emendrix.eu/acts/$act/$slug/" \
            "Not legal advice: computed by emendrix from published texts, with no lawyer's review.")"

The emendrix-snippet/1 user agent lets the example's use be counted from the server's own request lines, with no cookies and no tracking; change it if you would rather not be counted.