How We Know a Law Changed: Boards, Diffs, and the False Positives Nobody Talks About

A refresh re-pulls a government source on its cadence, reconciles what came back against what is already stored, and any difference in a section's stored text becomes a change event with a per-section diff, logged against that refresh run. That is the whole mechanism. The hard part is not detecting differences, it is deciding which differences mean the law changed, because most of them do not.

This post covers the detection side: how a change is found, how the registry of watched sources works, and the false positive classes anyone running this at scale has already met. The subscriber side, creating a watch, webhook delivery, retries and the deliveries endpoint, is covered in our post on law change webhooks.

TL;DR

  • A change event is a detected difference between two retrievals. It is not an official effective-date announcement, and treating it as one will make your product wrong in public.
  • Boards are the registry of watchable sources, keyed by corpus type plus jurisdiction, with jurisdiction null for federal boards. Listing them costs nothing, and each row names when the source was last read, on what interval, and how that read went.
  • The dominant failure mode is false positives, not missed changes: publisher template edits, whitespace and encoding normalization, renumbering, and pages that re-render differently all produce a diff with no legal content behind them.
  • A fetch failure must never become a deletion. A corpus that reports missing sections as removals will destroy itself on the day its publisher has an outage.
  • Different corpora change in different shapes. Bulletins and Federal Register documents are append-mostly, so a new document is the signal. Statutes and regulations are edit-in-place, so a diff is the signal.
  • Poll the changes endpoint as the record. Treat the webhook as a latency optimization, because any push channel can drop a message.

A five-step flow from refreshing a source through reconciling against stored text, diffing the section, raising a change event, and firing a webhook.

4-question check
Question 1 of 4

What exactly is a change event in this design?

This post belongs to our legal data infrastructure series.

Detection only runs as often as the refresh does, which is How Fast Should Legal Data Refresh? Our Path From Monthly to Six Hours. The delivery side of what detection produces is Law Change Alerts API: Webhooks for When a Statute or Rule Actually Moves.

The mechanism, drawn

Every tracked source runs on a cadence. The run fetches, extracts, and rebuilds sections with the same identity rules used at first ingest, then compares each one against what is stored. Byte-identical sections produce nothing. A section whose text differs produces a change event carrying a per-section diff, attached to that refresh run so you can always ask which pull found it.

Loading diagram...

Two branches carry most of the weight. The failure branch never writes deletions. And rebuilding uses the same identity rules as the original ingest, so a section keeps its stable actId across runs. Without that, every publisher reformat would read as a mass deletion followed by a mass creation.

List what is watchable before you watch anything:

curl -s https://api.vaquill.ai/api/v1/boards \
  -H "Authorization: Bearer $VQ_KEY" \
  | jq '.data[] | select(.corpusType=="CFR") | {boardId, corpusType, jurisdiction, cadence, lastRetrievedAt, retrievalStatus}'
{
  "boardId": "cfr-federal",
  "corpusType": "CFR",
  "jurisdiction": null,
  "cadence": "daily",
  "lastRetrievedAt": "2026-08-30T04:12:07Z",
  "retrievalStatus": "current"
}

retrievalStatus is the field that decides whether silence means anything. On anything other than current, a watch that has not fired tells you nothing. The call is free.

Boards: the registry

A board is one tracked source, keyed by corpus type plus jurisdiction. REGULATION plus tx is one board, STATE plus ca is another, and CFR is a board with a null jurisdiction because there is no state to scope it to. Every federal corpus works that way.

GET /boards is free and returns, per source, when it was last retrieved, what cadence it runs on, and its current retrieval status. No credit charge, no plan gate.

The practical use is a preflight. Before you build a workflow on a source, call /boards and look at the three fields together. A weekly cadence with a lastRetrievedAt from five weeks ago tells you something the marketing page will not.

The companion call is GET /us/statutes/coverage, also free and documented in the coverage guide, which carries a freshness array declaring every paused corpus with its reason. A paused corpus is complete and officially sourced, it is simply not re-pulling, most often because the publisher's own access rules require it. No watch on one can fire, because detection needs two retrievals and a paused source only ever produces one, which is why you read that array before wiring an alert.

The qualification: what a change event is not

A change event is a detected difference between two retrievals. It is not an announcement that a law took effect, it is not a legislative event, and it is not a legal conclusion.

Government publishers restate pages constantly. They fix typos, change templates, renumber headings, and migrate a title to a new CMS so every page in it re-renders with different whitespace. None of that is the law changing, and all of it produces a diff.

The reverse is also true and gets discussed even less. A bill can be signed today and not appear in the publisher's consolidated text for weeks or months, because the publisher's own compilation cycle runs on its own schedule. Colorado publishes its statutes once a year. No detection system, however fast, can find a change in text the publisher has not printed yet.

The false positive classes you should expect

Four families cover most of what you will see.

ClassWhat it looks likeWhy it happens
Publisher template and boilerplateEvery section in a title changes at once, and the diffs share a common regionA site redesign, a new footer, a changed disclaimer block, a version stamp printed into the page
Whitespace and encoding normalizationThe diff looks empty or nearly soNon-breaking spaces, changed line wrapping, a source switching from Latin-1 to UTF-8, a section symbol re-encoded
Renumbering that moves text without changing itOne section disappears and another appears with the same bodyThe publisher reorganized a chapter; the words are identical, the address moved
Re-render differencesIntermittent diffs that come and go between runsDynamic pagination, an A/B tested template, a PDF regenerated with a new toolchain, ordering that is not stable across requests

The first two are handled at extraction and normalization time, before reconcile ever sees them. The third is handled by stable identity, which I come back to below. The fourth is the nastiest, because it is not deterministic, and a source that flaps between two renderings will produce events forever until someone looks at it.

I am not going to publish a single false positive rate for the feed, and I would push back on any vendor who quotes you one without saying how it was measured. A single percentage in this domain is usually three different things stacked on top of each other.

There is a measured example of the same collapse next door. Our 2026-08-30 amendment-history census found that 912,388 of 3,518,180 state-corpora sections, 25.9%, carry no amendment year at all, and that one number turns out to hide three unrelated causes plus a fourth that is not a defect at all. The breakdown, and the Delaware case that proves an unsupported field can be entirely correct, is in Amendment History and Point-in-Time Law.

Four rules that keep detection safe

These are not style preferences. Each one exists because the alternative fails in a specific, expensive way.

Rule 1: a fetch failure must never become a deletion

If a source times out, returns a 500, serves a bot wall, or comes back with a partial document, the correct behavior is to keep everything already stored and mark the run as failed. Nothing gets removed.

A reconcile step that treats "not present in this pull" as "deleted from the corpus" will delete an entire jurisdiction the first time that publisher has an outage, and it will do it silently, because a clean empty response looks exactly like a successful pull of a source that no longer publishes anything. The corpus destroys itself on the publisher's bad day, not on yours. The rule generalizes to any pipeline mirroring an external system you do not control.

Rule 2: a failing source must be visible, not silently stale

The second-worst outcome after a wrong deletion is a source that quietly stops refreshing while continuing to serve. Search returns results, sections resolve, and the data is frozen at some date nobody can name.

That is what retrievalStatus on /boards is for, next to lastRetrievedAt and cadence. A paused or failing source says so in the free response, so you can read the timestamp instead of trusting that the pipeline is healthy. The same principle drives the freshness array on the coverage endpoint: a declared gap is a fact a customer can plan around, and an undeclared gap is a surprise during an audit.

Rule 3: detection runs against a stable identity

Every section carries a stable actId that survives a rebuild. USC_T42_C21_S1983 is the same identity across every refresh of the US Code, and it is not something you construct by hand, you take it from a search or resolve response.

Stable identity is what lets a renumbering be reported as a renumbering. Without it, a publisher moving a section between chapters produces one deletion plus one creation, and a customer watching that chapter gets told a law was repealed and a new one enacted. Both are wrong. The metadata carries the follow-through: renumberedTo and transferredTo chase a citation that moved, alongside supersedes and supersededBy.

Rule 4: gates fail closed, and runs keep receipts

The ingest pipeline that feeds detection runs a set of named gates that default to fail rather than publish, covering identity, text quality, truncation, chunk counts, excluded material, third-party rendering, identifier collisions, publisher origin, and record shape. A truncated document that reached storage would produce a change event on every section it touched, and that event would describe a pipeline defect rather than a change in the law, which is exactly what the gates exist to stop.

Alongside them sit per-run audit records rather than log lines: what each candidate document became and why it was kept or discarded, the structure a run found, every corrective write applied, and what a run actually wrote. A ledger is how you answer, weeks later, what happened to a document you expected and did not get.

Honest limit: these gates catch structural and identity defects, not every semantic error, and a gate's measured defect rate is a floor rather than a ceiling, because it only sees the shapes it was written to see.

Which corpora are worth watching, and why they differ

The kind of change a corpus produces decides what you watch for. A feed that treats all sources the same will be noisy on half of them.

Append-mostly corpora publish new documents and rarely edit old ones. The Federal Register, state insurance bulletins, and agency guidance work this way, so a new document is the signal and diffing an old one mostly finds template drift.

Edit-in-place corpora rewrite existing text under a stable citation. Statutes, regulations, and court rules work this way, so a diff on an existing section is the signal.

Append plus revocation is its own shape. Executive actions accumulate, but the interesting event is usually a status change on an existing one, which is why rescindedOn matters more than a text diff there.

CorpuscorpusTypeSectionsChange shapeCadence today
Code of Federal RegulationsCFR219,114Edit in placeDaily
Executive Orders and Presidential DocumentsEXECUTIVE_ACTION3,788Append plus revocationDaily
Federal Register agency rulesFEDERAL_REGISTER202,526Append mostlyWeekly
Statutes at Large, 113th to 119th CongressesSESSION_LAW110,287Append only, historical recordWeekly
Federal agency guidance, 34 named sourcesAGENCY_GUIDANCE21,906Append mostlyWeekly
State insurance bulletins, 49 jurisdictionsSTATE_AGENCY_GUIDANCE13,178Append mostlyWeekly
United States Code, 2024 editionUSC60,170Edit in place, by editionChecked weekly for a new edition
State statutory codesSTATE~1.93M across 52Edit in place24 jurisdictions on a 7-day full re-pull, rest monthly or as of ingest
State administrative regulationsREGULATION~1.46M across 52Edit in placeMonthly or as of ingest
Federal Rules of Procedure and Supreme Court RulesFEDERAL_RULES589Edit in placeMonthly
US Sentencing GuidelinesSENTENCING_GUIDELINES302Edit in placeAnnual

Two rows deserve a note. SESSION_LAW items carry actStatus: "enacted" and goodLawStatus: "unknown" on purpose, because they are law as enacted, a historical record rather than a statement of current law. And the USC row means we check weekly for a new edition, not for edits to the existing one, because the publisher does not work that way.

That is the general point. A cadence faster than the publisher's own cycle is theater. Polling a state that compiles its statutes annually every six hours cannot make the law newer, it just burns requests on a government server. Four things gate a tighter cadence, in order, and they are worked through one at a time in our cadence roadmap.

A consumption pattern that survives contact with production

The design I would build against, and ask any vendor to support:

1. Poll the changes endpoint as your record of truth. It is ordered, replayable, and you control the cursor. Store the last change you processed. On every poll, ask for everything after it.

2. Treat the webhook as a latency optimization. Webhook delivery for law change alerts exists today, along with endpoints to list changes, fetch a diff, list deliveries, and send a test, all four in the law change alerts reference. Use it to wake your worker sooner, not as the only path a change can reach you, because every push channel drops messages eventually. The delivery mechanics and retries are covered in the law change webhooks post.

3. Reconcile on a schedule. Once a day, re-poll a window wider than your last cursor and compare against what you processed. A change in the pull that your webhook handler never saw is a dropped delivery, found by you rather than by your customer.

4. Fetch the diff, do not guess from the event. The event says a section changed. The diff says what changed, which is what decides whether a human needs to look. A one-character edit in a boilerplate footer and a rewritten liability standard are the same event type and very different work.

5. Check /boards before you trust silence. No events for a week could mean nothing changed, or that the source has not been retrieved for a week. Those look identical from the changes endpoint. Alert on lastRetrievedAt drifting past the declared cadence and you will catch a stalled source without waiting for someone to notice. The coverage monitoring recipe writes that check out.

The alerts page covers the product surface for this if you want the non-API version.

How to evaluate a change feed from any vendor

Five questions. They work on us and they work on everyone else, and the answers separate a real detection system from a cron job with a mailing list.

1. What counts as a change? Ask for the definition in writing. If the answer is a phrase like "we monitor legislative activity," push until you get a mechanism. Is it a text diff, a publisher-announced effective date, a bill-tracking feed, or a human reading a newsletter? All four are legitimate products and they are not interchangeable.

2. What is your false positive rate, and how did you measure it? The method matters more than the number. A rate measured over one corpus for one month does not generalize. A vendor who declines to give one number because it varies by source and by cause, and then explains the causes, is more useful than one who quotes you 2 percent.

3. What happens when a fetch fails? The answer you want is that stored data is kept and the run is flagged. If a vendor cannot answer immediately, they may not have decided, which means the behavior is whatever the code happens to do.

4. Can I see the diff? An event with no diff is an interrupt with no information. You will end up re-fetching and comparing yourself, which means paying for a feed and doing the work anyway.

5. Can I verify last retrieval time myself, for free, per source? This is the one that most often comes back no. Freshness that can only be confirmed by asking the vendor is a promise rather than a verifiable claim.

One thing detection cannot do

Change events tell you that text changed between two retrievals. They do not give you the text as it stood on an arbitrary past date.

Detection compares retrieval N against retrieval N+1, and what it stores at the end is one text per citation, the newest one. So there is no as_of=DATE query, no version list per section, and no arbitrary-date diff. What exists is four narrower things: amendment history and last-amended year per section, a yearFrom/yearTo currency filter that filters on the last amendment year the publisher credits (so the roughly one in five sections with no credit drop out once either bound is set), change events captured per refresh, and per-section diffs on a watched board.

If your use case is "show me 26 U.S.C. 163 as it read in March 2019," none of those four answer it, and you should know that before you build. Amendment History and Point-in-Time Law covers what to do instead. If your use case is "tell me when the sections I care about move, and show me what moved," that is exactly what this is for.

FAQ

It is a record that a section's stored text differs from what came back on the most recent retrieval, carrying a per-section diff and logged against the refresh run that found it. It signals that something on the publisher's page changed, not that the law changed.

Does a change event mean a law took effect?

No. Effective dates are set by the legislature or agency and announced by the publisher, and they often precede or follow the day the consolidated text is updated. A change event only tells you the published text moved. Read the diff and the publisher's own page before drawing a conclusion.

Four common causes: a changed page template or boilerplate, whitespace or encoding normalizing differently, a renumbering that moved text without changing it, or a page that re-rendered differently between requests. The first two are handled during extraction, and the third by stable section identity so a move reads as a move.

How do I check when a source was last refreshed?

Call GET /boards, which costs nothing. Every watchable source comes back with the timestamp of its last clean retrieval, the interval it is meant to run on, and the status of that retrieval. Compare the first against the second, and alert if it has drifted.

What happens to my data if the government site goes down?

Nothing gets deleted. A failed or partial fetch keeps the stored corpus intact and marks the run as failed, which shows up in retrievalStatus. A pipeline that treated a failed fetch as a set of deletions would wipe out a jurisdiction the first time its publisher had an outage.

What is a board, and how is it identified?

A board is one tracked source. It is keyed by corpus type plus jurisdiction, for example REGULATION plus tx. Federal boards such as CFR carry a null jurisdiction because there is no state to scope them to.

Should I use webhooks or polling for law change alerts?

Both, with polling as the record. Poll the changes endpoint with a stored cursor so you can replay, use the webhook to cut latency, and reconcile the two daily so a dropped delivery is found by you rather than your customer.

Can I get the text of a statute as it read on a past date?

Versioning here runs off the amendment record rather than a date parameter. Only the current text of each citation is kept, so there is no as_of=DATE query and no per-section version list to page through. What is available is amendment history and last-amended year per section, a yearFrom/yearTo currency filter, change events per refresh, and per-section diffs on a watched board.

Why do different corpora refresh at different speeds?

Because the publishers do. The Code of Federal Regulations and executive actions refresh daily, the Federal Register and agency guidance weekly, and a state that compiles its statutes once a year cannot be made fresher by polling it more often. The other limits are whether a source can be pulled whole, the publisher's own rules on automated access, and reconcile cost.

What does it mean when a corpus is listed as paused?

It means the data is complete and officially sourced but is not currently re-pulling, most often because the publisher's own access rules require it. Every pause is declared with its reason in the freshness array of the free coverage response, so you read it from the API rather than discover it in production.

The most complete US primary law API.
Every US statute, regulation, constitution, and executive order through one REST and MCP API. 4M+ sections, section-level citations, and links to the official source. Plus a free open dataset.
23 min read

New legal AI guides, weekly.

Priyansh Khodiyar

Priyansh Khodiyar

Co-Founder & CTO

Priyansh leads engineering and AI at Vaquill, from the matter workbench to drafting, document comparison, document matrix, and citation-verified research.