{"openapi":"3.1.0","info":{"title":"AI Law Tracker — Public Laws API","version":"v1","description":"Read-only, versioned JSON API over the audited AI-regulation dataset (US state + federal, EU, and global). FREE beta. Data is CC BY 4.0 — attribution required. Informational only, not legal advice.","license":{"name":"CC BY 4.0","url":"https://creativecommons.org/licenses/by/4.0/"}},"servers":[{"url":"https://ai-law-tracker.com/api/v1"}],"security":[{},{"ApiKeyAuth":[]},{"BearerAuth":[]}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key"},"BearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Record":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Stable primary key (the /v1/laws/{id} path key)."},"scope":{"type":"string","enum":["state","federal","eu","global"]},"jurisdiction":{"type":"object","properties":{"slug":{"type":"string","example":"colorado"},"name":{"type":"string","nullable":true,"example":"Colorado"},"abbr":{"type":"string","nullable":true,"example":"CO"}}},"identifier":{"type":"string","nullable":true,"example":"HB 251212"},"title":{"type":"string","nullable":true},"record_type":{"type":"string","nullable":true,"example":"bill"},"status":{"type":"string","nullable":true,"description":"Rich free-text status verbatim from the source."},"in_force":{"type":"boolean","nullable":true,"description":"Set only where the source states it explicitly; else null."},"record_date":{"type":"string","format":"date-time","nullable":true},"summary":{"type":"string","nullable":true},"source":{"type":"string","nullable":true,"example":"openstates.org"},"official_url":{"type":"string","nullable":true,"description":"Primary/official (.gov) link where available."},"updated_at":{"type":"string","format":"date-time","nullable":true},"attribution":{"type":"string","example":"Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com"}}},"Change":{"type":"object","description":"One observed change to a record (insert or update).","properties":{"event_id":{"type":"string","format":"uuid","description":"Opaque, unique per change (append-only history row id)."},"entity":{"type":"string","enum":["law_record","jurisdiction"],"description":"Which dataset changed."},"record_id":{"type":"string","format":"uuid","nullable":true,"description":"The /v1/laws/{id} key (law_record only; null for jurisdiction)."},"scope":{"type":"string","enum":["state","federal","eu","global"],"nullable":true},"jurisdiction":{"type":"object","properties":{"slug":{"type":"string","example":"washington"},"name":{"type":"string","nullable":true,"example":"Washington"},"abbr":{"type":"string","nullable":true,"example":"WA"}}},"identifier":{"type":"string","nullable":true,"example":"HB 1170"},"title":{"type":"string","nullable":true},"change_kind":{"type":"string","enum":["insert","update"]},"changed_fields":{"type":"array","nullable":true,"description":"Public field names that changed on an update (null on insert). Internal metadata churn appears as \"metadata\".","items":{"type":"string"}},"field_changes":{"type":"array","description":"Compact from→to value diff for the scalar fields that changed (empty on insert or a metadata-only update).","items":{"type":"object","properties":{"field":{"type":"string","example":"status"},"from":{},"to":{}}}},"observed_at":{"type":"string","format":"date-time","description":"When this state was first observed. Use as the ?since= cursor."},"source_run_id":{"type":"string","format":"uuid","nullable":true,"description":"The scrape run that produced this state."},"attribution":{"type":"string","example":"Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com"}}},"Webhook":{"type":"object","description":"A change-notification subscription.","properties":{"id":{"type":"string","format":"uuid"},"target_url":{"type":"string","description":"HTTPS endpoint that receives signed POSTs."},"events":{"type":"array","items":{"type":"string","enum":["law.created","law.updated"]}},"scope":{"type":"string","enum":["state","federal","eu","global"],"nullable":true,"description":"Optional filter — only changes in this scope."},"jurisdiction":{"type":"string","nullable":true,"description":"Optional filter — only changes for this jurisdiction slug."},"active":{"type":"boolean"},"failure_count":{"type":"integer","description":"Consecutive failed deliveries; the webhook auto-disables at 10."},"disabled_at":{"type":"string","format":"date-time","nullable":true},"disabled_reason":{"type":"string","nullable":true},"last_delivery_at":{"type":"string","format":"date-time","nullable":true},"created_at":{"type":"string","format":"date-time"}}},"WebhookPayload":{"type":"object","description":"The signed POST body a subscriber receives for one change.","properties":{"event":{"type":"string","enum":["law.created","law.updated"]},"event_id":{"type":"string","format":"uuid","description":"Unique per change (append-only history row id)."},"delivery_id":{"type":"string","format":"uuid","description":"Unique per delivery attempt — idempotency key for the receiver."},"webhook_id":{"type":"string","format":"uuid"},"change_kind":{"type":"string","enum":["insert","update"]},"changed_fields":{"type":"array","nullable":true,"items":{"type":"string"},"description":"Fields that changed on an update (null on insert)."},"observed_at":{"type":"string","format":"date-time"},"record":{"$ref":"#/components/schemas/Record"},"attribution":{"type":"string","example":"Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com"}}},"Error":{"type":"object","properties":{"api_version":{"type":"string"},"error":{"type":"object","properties":{"code":{"type":"string","example":"rate_limited"},"message":{"type":"string"},"docs":{"type":"string"},"retry_after":{"type":"integer","description":"Seconds (429 only)."}},"required":["code","message"]}}}}},"paths":{"/health":{"get":{"summary":"Liveness + backend status","responses":{"200":{"description":"OK"}}}},"/laws":{"get":{"summary":"List legal records (filter, sort, paginate)","parameters":[{"name":"scope","in":"query","schema":{"type":"string","enum":["state","federal","eu","global"]},"description":"Filter by scope."},{"name":"jurisdiction","in":"query","schema":{"type":"string"},"description":"Exact jurisdiction slug (e.g. colorado, eu, us-federal, canada)."},{"name":"status","in":"query","schema":{"type":"string"},"description":"Case-insensitive substring match on status."},{"name":"in_force","in":"query","schema":{"type":"boolean"},"description":"Filter by the in_force flag."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only records updated at/after this ISO timestamp."},{"name":"q","in":"query","schema":{"type":"string"},"description":"Substring search over title + summary."},{"name":"sort","in":"query","schema":{"type":"string","enum":["updated_at","record_date","title"],"default":"updated_at"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"A page of records.","content":{"application/json":{"schema":{"type":"object","properties":{"api_version":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Record"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"count":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"}}}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/laws/{id}":{"get":{"summary":"Get one record by uuid","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The record","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Record"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/laws/{id}/history":{"get":{"summary":"Full change timeline for one record","description":"Every snapshot of the record (ascending observed_at), each with change_kind, changed_fields and the record values as of that snapshot.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The record timeline.","content":{"application/json":{"schema":{"type":"object","properties":{"api_version":{"type":"string"},"data":{"type":"object","properties":{"record_id":{"type":"string","format":"uuid"},"count":{"type":"integer"},"timeline":{"type":"array","items":{"type":"object","properties":{"observed_at":{"type":"string","format":"date-time"},"change_kind":{"type":"string","enum":["insert","update"]},"changed_fields":{"type":"array","nullable":true,"items":{"type":"string"}},"source_run_id":{"type":"string","format":"uuid","nullable":true},"record":{"$ref":"#/components/schemas/Record"}}}}}}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/changes":{"get":{"summary":"Unified changelog (poll for what changed since X)","description":"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>. Free-beta clamps the lookback window per tier (see meta.window).","parameters":[{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only changes observed after this ISO timestamp (pass the previous response meta.cursor to poll)."},{"name":"scope","in":"query","schema":{"type":"string","enum":["state","federal","eu","global"]},"description":"Filter by scope."},{"name":"jurisdiction","in":"query","schema":{"type":"string"},"description":"Exact jurisdiction slug."},{"name":"entity","in":"query","schema":{"type":"string","enum":["law_record","jurisdiction"]},"description":"Restrict to one dataset (default: both)."},{"name":"significant","in":"query","schema":{"type":"boolean","default":false},"description":"Drop internal metadata-only updates (keeps inserts + real field changes)."},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"},"description":"Order by observed_at."},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"A page of changes.","content":{"application/json":{"schema":{"type":"object","properties":{"api_version":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Change"}},"meta":{"type":"object","properties":{"total":{"type":"integer"},"count":{"type":"integer"},"limit":{"type":"integer"},"offset":{"type":"integer"},"order":{"type":"string","enum":["asc","desc"]},"cursor":{"type":"string","format":"date-time","nullable":true,"description":"Newest observed_at on this page; pass as ?since= to poll."},"window":{"type":"object","properties":{"tier":{"type":"string"},"lookback_days":{"type":"integer","nullable":true,"description":"null = unbounded (paid tiers)."},"clamped":{"type":"boolean","description":"true when the requested since was older than the tier window."}}}}}}}}}},"400":{"description":"Invalid parameter","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/feed":{"get":{"summary":"Lean stream of recent significant changes","description":"Convenience view over /changes with significant=true and no value diffs — the newest meaningful changes, human-scannable.","parameters":[{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only changes observed after this ISO timestamp (pass the previous response meta.cursor to poll)."},{"name":"scope","in":"query","schema":{"type":"string","enum":["state","federal","eu","global"]},"description":"Filter by scope."},{"name":"jurisdiction","in":"query","schema":{"type":"string"},"description":"Exact jurisdiction slug."},{"name":"entity","in":"query","schema":{"type":"string","enum":["law_record","jurisdiction"]},"description":"Restrict to one dataset (default: both)."},{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":100}}],"responses":{"200":{"description":"Recent significant changes."},"503":{"description":"Backend unavailable","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/jurisdictions":{"get":{"summary":"List covered jurisdictions with record counts","parameters":[{"name":"scope","in":"query","schema":{"type":"string","enum":["state","federal","eu","global"]}}],"responses":{"200":{"description":"Jurisdiction index"}}}},"/search":{"get":{"summary":"Search records by title/summary text","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string"}},{"name":"scope","in":"query","schema":{"type":"string","enum":["state","federal","eu","global"]},"description":"Filter by scope."},{"name":"jurisdiction","in":"query","schema":{"type":"string"},"description":"Exact jurisdiction slug (e.g. colorado, eu, us-federal, canada)."},{"name":"status","in":"query","schema":{"type":"string"},"description":"Case-insensitive substring match on status."},{"name":"in_force","in":"query","schema":{"type":"boolean"},"description":"Filter by the in_force flag."},{"name":"updated_since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only records updated at/after this ISO timestamp."},{"name":"sort","in":"query","schema":{"type":"string","enum":["updated_at","record_date","title"],"default":"updated_at"}},{"name":"order","in":"query","schema":{"type":"string","enum":["asc","desc"],"default":"desc"}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}},{"name":"offset","in":"query","schema":{"type":"integer","default":0}}],"responses":{"200":{"description":"Ranked results"},"400":{"description":"Missing q","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/countries":{"get":{"summary":"National (global-scope) jurisdictions with record counts","responses":{"200":{"description":"Country index"},"503":{"description":"Backend unavailable"}}}},"/states":{"get":{"summary":"US state jurisdictions with record counts","responses":{"200":{"description":"State index"},"503":{"description":"Backend unavailable"}}}},"/categories":{"get":{"summary":"Dataset facets: record_type + scope distributions","responses":{"200":{"description":"Category facets ({ record_types[], scopes[] } of { value, count })."},"503":{"description":"Backend unavailable"}}}},"/keys":{"post":{"summary":"Self-serve: issue a FREE API key by email","description":"Submit an email to mint a free-tier key. The plaintext key is emailed to that address AND returned once in this response — it is never stored and cannot be recovered. One active free key per email; abusive request rates are throttled per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Where the key is delivered."}}}}}},"responses":{"201":{"description":"Key issued (plaintext shown once).","content":{"application/json":{"schema":{"type":"object","properties":{"api_version":{"type":"string"},"key":{"type":"string","example":"alt_free_…","description":"Plaintext key — shown once, not recoverable."},"key_prefix":{"type":"string","example":"alt_free_ab12"},"id":{"type":"string","format":"uuid"},"tier":{"type":"string","example":"free"},"email":{"type":"string"},"email_delivery":{"type":"object","properties":{"delivered":{"type":"boolean"}}}}}}}},"400":{"description":"Invalid email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"A key already exists for this email","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many key requests","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Key store not available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks":{"post":{"summary":"Subscribe to change events (requires an API key)","description":"Create a webhook. When a matching legal record change is observed, an HMAC-SHA256-signed POST is delivered to target_url. The signing_secret is returned ONCE in this response. Verify each delivery (see x-webhooks).","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["target_url"],"properties":{"target_url":{"type":"string","description":"Public HTTPS endpoint."},"events":{"type":"array","items":{"type":"string","enum":["law.created","law.updated"]},"description":"Default: law.created, law.updated."},"scope":{"type":"string","enum":["state","federal","eu","global"],"description":"Optional filter."},"jurisdiction":{"type":"string","description":"Optional exact-slug filter."}}}}}},"responses":{"201":{"description":"Created (includes signing_secret, shown once).","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Webhook"}}}}}},"400":{"description":"Invalid target_url or events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Per-key webhook limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Webhooks not enabled yet","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"summary":"List this key's webhooks","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"responses":{"200":{"description":"The key’s subscriptions.","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"401":{"description":"API key required","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/webhooks/{id}":{"get":{"summary":"Get one subscription","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The subscription","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"$ref":"#/components/schemas/Webhook"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Unsubscribe","security":[{"ApiKeyAuth":[]},{"BearerAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Deleted"},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"x-webhooks":{"description":"Outbound change notifications. Subscribe with POST /v1/webhooks; deliveries are POSTed to target_url with an HMAC-SHA256 signature.","events":["law.created","law.updated"],"payload":{"$ref":"#/components/schemas/WebhookPayload"},"headers":{"X-ALT-Signature":"sha256=<hex HMAC-SHA256 of `${timestamp}.${rawBody}` under your signing_secret>","X-ALT-Timestamp":"Unix seconds; folded into the signed content (reject if skew is large).","X-ALT-Event":"law.created | law.updated","X-ALT-Delivery":"Delivery uuid — use as an idempotency key.","X-ALT-Webhook-Id":"The subscription uuid."},"delivery":"At-least-once. Up to 6 attempts with exponential backoff; 10 consecutive failures auto-disable the webhook. De-duplicate on X-ALT-Delivery / event_id.","verify_example_node":"const crypto = require('crypto');\nfunction verify(req, secret) {\n  const ts = req.headers['x-alt-timestamp'];\n  const sig = (req.headers['x-alt-signature'] || '').replace(/^sha256=/, '');\n  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${req.rawBody}`).digest('hex');\n  const a = Buffer.from(sig), b = Buffer.from(expected);\n  return a.length === b.length && crypto.timingSafeEqual(a, b);\n}"},"x-rate-limits":{"description":"Best-effort per-client limits during beta. Every response carries X-RateLimit-Limit/Remaining/Reset. 429 includes Retry-After. Get a free key at https://ai-law-tracker.com/developers.","anonymous":"20 req/min, 500 req/day (per IP, no key)","free_key":"60 req/min, 2000 req/day","developer":"120 req/min, 5000 req/day","pro":"300 req/min, 50000 req/day","business":"1000 req/min, 500000 req/day","enterprise":"custom (contact sales)"},"x-pricing":{"description":"Self-serve tiers. Free is live now (self-serve at /developers); paid tiers are billed per month. See https://ai-law-tracker.com/pricing.","tiers":[{"name":"Free","price_usd_month":0,"note":"Self-serve. Attribution (dofollow) required."},{"name":"Developer","price_usd_month":29},{"name":"Pro","price_usd_month":99,"note":"Webhooks + 90-day changelog window."},{"name":"Business","price_usd_month":299,"note":"Webhooks + full changelog history."},{"name":"Enterprise","price_usd_month":null,"note":"Quote. Custom limits, SLA, OEM/resale."}]},"x-attribution":"Data by AI Law Tracker (CC BY 4.0) — https://ai-law-tracker.com"}