---
title: "Unified changelog (poll for what changed since X)"
method: GET
path: "/changes"
---

# Unified changelog (poll for what changed since X)

`GET /changes`

## Request

- Base URL: `https://ai-law-tracker.com/api/v1`
- URL: `https://ai-law-tracker.com/api/v1/changes`
- Auth: one of:
  - none
  - API key in header `X-API-Key`
  - HTTP bearer

Time-ordered feed of every observed insert/update across the dataset, unioned from the append-only history of legal records and jurisdiction summaries. Poll with ?since=<meta.cursor>. TIER-GATING: Developer+ only — anonymous/Free callers get 403 tier_upgrade_required (the change feed is the paid differentiator). The lookback window is clamped per tier (Developer 30-day, Pro 90-day, Business/Enterprise full) — see meta.window.

## Query parameters

- `since` string, date-time
- `scope` 'state' | 'federal' | 'eu' | 'global'
- `jurisdiction` string
- `entity` 'law_record' | 'jurisdiction'
- `significant` boolean
- `order` 'asc' | 'desc'
- `limit` integer
- `offset` integer

## Response `200`

A page of changes.

- object
  - `api_version` string
  - `data` Change[]
    - `event_id` string, uuid: Opaque, unique per change (append-only history row id).
    - `entity` 'law_record' | 'jurisdiction': Which dataset changed.
    - `record_id` string, uuid, nullable: The /v1/laws/{id} key (law_record only; null for jurisdiction).
    - `scope` 'state' | 'federal' | 'eu' | 'global', nullable
    - `jurisdiction` object
      - `slug` string
      - `name` string, nullable
      - `abbr` string, nullable
    - `identifier` string, nullable
    - `title` string, nullable
    - `change_kind` 'insert' | 'update'
    - `changed_fields` string[], nullable: Public field names that changed on an update (null on insert). Internal metadata churn appears as "metadata".
    - `field_changes` object[]: Compact from→to value diff for the scalar fields that changed (empty on insert or a metadata-only update).
      - `field` string
      - `from` unknown
      - `to` unknown
    - `observed_at` string, date-time: When this state was first observed. Use as the ?since= cursor.
    - `source_run_id` string, uuid, nullable: The scrape run that produced this state.
    - `attribution` string
  - `meta` object
    - `total` integer
    - `count` integer
    - `limit` integer
    - `offset` integer
    - `order` 'asc' | 'desc'
    - `cursor` string, date-time, nullable: Newest observed_at on this page; pass as ?since= to poll.
    - `window` object
      - `tier` string
      - `lookback_days` integer, nullable: null = unbounded (paid tiers).
      - `clamped` boolean: true when the requested since was older than the tier window.

## Other responses

- `400`: Invalid parameter
- `429`: Rate limited
- `503`: Backend unavailable

---

[API](https://skmtc.dev/ai-law-tracker/apis/ai-law-tracker-public-laws-api.md) · [All operations](https://skmtc.dev/ai-law-tracker/apis/ai-law-tracker-public-laws-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ai-law-tracker/apis/ai-law-tracker-public-laws-api/revisions/25403268a853?raw)
