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 isn't detecting differences, it is deciding which differences mean the law changed, because most of them don't.
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 and the deliveries endpoint, is covered in our post on law change webhooks.
Reviewed and corrected on 12 October 2026. This is general information, not legal advice.
TL;DR
- A change event is a detected difference between two retrievals. It isn't 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 state, with no state on federal boards. Listing them costs nothing, and each row names when the source was last retrieved and on what cadence.
- 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 state publisher moves its site to a new template. Overnight your monitor reports that every section in one title changed, and the diffs all share the same footer text. What most likely happened?
This post belongs to our legal data infrastructure series.
Detection only runs as often as the refresh does, which is the subject of Why Legal Data Cannot Refresh Faster Than Its Publisher. 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.
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. The response below is the real Federal Register board as GET /boards returned it on 12 October 2026, trimmed to the fields the jq filter keeps:
curl -s https://api.vaquill.ai/api/v1/boards \
-H "Authorization: Bearer $VQ_KEY" \
| jq '.data.boards[] | select(.corpusType=="federal_register") | {corpusType, label, cadence, lastRetrievedAt}'
{
"corpusType": "federal_register",
"label": "Federal Register",
"cadence": "daily",
"lastRetrievedAt": "2026-10-12T06:00:00.17003+00:00"
}
Compare lastRetrievedAt against cadence before you treat silence on a watch as "nothing changed". It records when we last checked, which says nothing about when the law last changed. The call is free.
Boards: the registry
A board is one tracked source, keyed by corpus type plus jurisdiction. state_regulation plus tx is one board, state plus ca is another, and cfr is a board with no state because there is none to scope it to. Every federal corpus works that way.
GET /boards is free and returns, per source, lastRetrievedAt and cadence. It needs an API key and costs no credits.
The practical use is a preflight. Before you build a workflow on a source, call /boards and look at the two fields together. A weekly cadence with a lastRetrievedAt from five weeks ago tells you something the marketing page won't.
The companion call is GET /us/statutes/coverage, also free and documented in the coverage guide. It reports what we hold and says nothing about how recently it was refreshed, and it points to /boards for the age of any source. Read the two together so a section count carries an age. Detection needs two successful retrievals to compare, so compare lastRetrievedAt against cadence before you wire an alert.
The qualification: what a change event is not
A change event is a detected difference between two retrievals. It isn't an announcement that a law took effect, it isn't a legislative event, and it isn't 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's Office of Legislative Legal Services prints its statutes each year. No detection system, however fast, can find a change in text the publisher hasn't printed yet.
Publishers say so themselves. The Office of the Law Revision Counsel prepares the U.S. Code for the House, and its About page says of the printed edition: "there is necessarily a lag between the date a law is enacted and the date on which it appears in the printed version of the Code." The online Code is fresher, and it flags its own gap. Each section shows a currency date, and the OLRC's Classification Tables list the recently enacted laws that affect sections (see its Currency and Updating page). The UK's legislation.gov.uk is just as candid. Its pages carry the notice "Revised legislation carried on this site may not be fully up to date," and list changes "yet to be applied" by its editorial team.
The consequence for a diff-based detector is easy to miss. It sees a change when the editors catch up, which can be well after the law moved. That gap is why a change event can't stand in for an effective date.
The false positive classes you should expect
Four families cover most of what you will see.
| Class | What it looks like | Why it happens |
|---|---|---|
| Publisher template and boilerplate | Every section in a title changes at once, and the diffs share a common region | A site redesign, a new footer, a changed disclaimer block, a version stamp printed into the page |
| Whitespace and encoding normalization | The diff looks empty or nearly so | Non-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 it | One section disappears and another appears with the same body | The publisher reorganized a chapter; the words are identical, the address moved |
| Re-render differences | Intermittent diffs that come and go between runs | Dynamic pagination, an A/B tested template, a PDF regenerated with a new toolchain, ordering that isn't 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 Rule 3 below covers. The fourth is the nastiest, because it isn't deterministic, and a source that flaps between two renderings will produce events forever until someone looks at it.
No single false positive rate is published for the feed here, and any vendor who quotes you one should say how it was measured. A single percentage in this domain is usually several different causes stacked on top of each other, so ask for the breakdown, not the headline.
A quick story: the section the Code said was repealed
Absence makes a bad signal, and the best example comes from the Supreme Court.
In the Act of September 7, 1916, Congress let a national bank in a small town act as an insurance agent. When the first U.S. Code appeared in 1926, that provision was printed as section 92 of Title 12. The 1952 edition left it out, with a note saying Congress had repealed it in 1918. Congressional staff later looked at the question and agreed with the codifiers. For decades the compiled text told every reader the provision was gone.
Then in 1986 the Comptroller of the Currency relied on section 92 to let a national bank in a small Oregon town sell insurance to customers nationwide, and insurance-agent trade groups sued. The case, United States National Bank of Oregon v. Independent Insurance Agents of America, reached the Supreme Court. On June 7, 1993, Justice Souter wrote for the Court: "We hold that no repeal occurred."
The reasoning is the part for developers. The Code is only "prima facie" evidence of the law for most titles, and "it is the Statutes at Large that provides the 'legal evidence of laws.'" So, "despite its omission from the Code section 92 remains on the books if the Statutes at Large so dictates." The codifiers had read a law as gone, and the law's own text said otherwise. You can read the full opinion at the Library of Congress.
A system that treats "missing from this pull" as "repealed" makes the same mistake those codifiers did, only faster. Hold that thought for Rule 1 below.
What developers say
People who run scrapers against government sources tend to describe the same two problems. One is churn. A commenter who says they have worked on legislative data for 15 years, including open source scrapers with OpenStates, wrote that "the sources constantly change and break in goofy ways." They add that unlike web search, where you can ignore broken pages, "the penalty for missing things is too high." (showerst, Hacker News, March 2026). That is the false positive table above seen from the operator's chair. Pages churn, and you can't skip the odd-looking ones.
The other is the old dream of diffing the law. In 2014 a commenter noted that Congress had "recently converted the US Code into very good XML," and that "Anyone could copy it into github or run it through diff to see changes." (joshdata, Hacker News, October 2014). The idea holds up. What you get is a diff of what the editors have applied so far, which brings us back to the pending-updates problem.
Four rules that keep detection safe
These aren't 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 don't control.
Rule 2: the age of a source must be readable, not assumed
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 why /boards carries lastRetrievedAt next to cadence. Compare the two yourself in the free response, so you read the timestamp instead of trusting that the pipeline is healthy. The coverage endpoint follows the same principle: it sends you to /boards for age, so a count never travels without one.
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 isn't 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: validate before you compare, and keep receipts
Whatever feeds a detector should refuse bad input rather than pass it along. 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. So check identity, text completeness and record shape before a reconcile step sees the data, and fail closed when a check fails.
Keep a per-run record as well, not just log lines: what each run fetched, kept, discarded and wrote. That record is how you answer, weeks later, what happened to a document you expected and didn't get. Checks like these catch structural and identity defects, not every semantic error, because they only see the shapes they were 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.
| Corpus | corpusType | Sections | Change shape | Board cadence declared on 12 Oct 2026 |
|---|---|---|---|---|
| Code of Federal Regulations | CFR | 222,311 | Edit in place | daily |
| Executive Orders and Presidential Documents | EXECUTIVE_ACTION | 3,674 | Append plus revocation | No separate board in the federal listing; check GET /boards |
| Federal Register agency rules | FEDERAL_REGISTER | More than 196,000 | Append mostly | daily |
| Statutes at Large, 1789 to 2026 | SESSION_LAW | More than 425,000 | Append only, historical record | monthly |
| Federal agency guidance, 53 named sources | AGENCY_GUIDANCE | 96,966 | Append mostly | monthly (agency guidance board) |
| State insurance bulletins, 49 jurisdictions | STATE_AGENCY_GUIDANCE | 13,360 | Append mostly | monthly |
| United States Code | USC | 60,494 | Edit in place, by release | weekly (checked for a new release) |
| State statutory codes | STATE | 2M+ across 56 jurisdictions | Edit in place | monthly, with three states quarterly |
| State administrative regulations | REGULATION | 1.56M+ across 56 jurisdictions | Edit in place | monthly |
| Federal Rules of Procedure and Supreme Court Rules | FEDERAL_RULES | 1,130 | Edit in place | monthly |
| US Sentencing Guidelines | SENTENCING_GUIDELINES | 1,281 | Edit in place | monthly (shared board with US tax treaties) |
Read the cadence column as the staleness window a board declares, which is not necessarily how often it is re-pulled. The coverage guide notes, for example, that 25 state statute jurisdictions are re-pulled every 7 days while their boards declare monthly.
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 release, not for edits to the existing one, because the publisher doesn't 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 can't make the law newer, it just burns requests on a government server. Four things gate a tighter cadence, and Why Legal Data Cannot Refresh Faster Than Its Publisher works through them one at a time.
A consumption pattern that survives contact with production
The design to build against, and to 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. The endpoint covers the source's whole captured history, not only the period since you subscribed, and its events are swept after a fixed retention period, which the endpoint's reference states.
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. Everything in that family is free except the diff, which costs 4 credits, and the reference lists the plan limits on watches and on the webhook channel. 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 hasn't 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 aren't 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 doesn't 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 can't 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.
Detection and past text
Change events tell you that text changed between two retrievals. They are a feed of differences and make no promise about the text on an arbitrary past date.
Detection compares retrieval N against retrieval N+1 and keeps one current text per citation. Past text is a separate read: add asOf=YYYY-MM-DD to GET /us/statutes/section/{act_id}/body and the response names the engine that answered. On stored_edition (the CFR annual editions, the US Code, the Copyright Compendium and the OFAC FAQs), you get a printed edition we hold, and a date that falls between held editions comes back unavailable rather than answered from a neighbour. On observed_change, every other corpus, the earlier text is rebuilt from the before-side of the first change we observed after your date, and isBounded tells you when the answer rests on when capture began rather than on the law. Treat that result as a reconstruction and read isBounded before you cite it.
Alongside it sit 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," check asOf.engine and asOf.source on the answer first, because outside the CFR annual editions the editions we hold are sparse, so many historical dates in the US Code fall between them. Amendment History and Point-in-Time Law covers the limits in detail. If your use case is "tell me when the sections I care about move, and show me what moved," that is exactly what watches are for.
FAQ
What is a change event in a legal data API?
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.
Why would a change event fire when nothing legal changed?
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 lastRetrievedAt and its cadence. Compare the first against the second, and alert if it has drifted. lastRetrievedAt records when we last checked, which says nothing about when the law last changed.
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. 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 identified by corpus type plus state, for example state_regulation plus tx. Federal boards such as cfr carry no state because there is none 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?
Yes, with limits. Add asOf=YYYY-MM-DD to the section body call. For the CFR annual editions, the US Code, the Copyright Compendium and the OFAC FAQs the answer is a stored edition we hold, and a date between held editions comes back unavailable. For other corpora it is a reconstruction from observed changes, with isBounded telling you when it rests on when capture began. Alongside it you have 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 refreshes daily, other sources run on their own schedules (the coverage guide lists them), and a state that compiles its statutes once a year can't 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.
New legal AI guides, weekly.
Further Reading
Amendment History and Point-in-Time Law: What a Legal API Can and Cannot Tell You
Read postHybrid Search Over 20 Million Legal Passages: Why Semantic Alone Fails on Law
Read postParsing Legal Citations in Code: Bluebook Forms, State Variants, and the Ones You Must Refuse
Read postLegal Citation Resolution: Turning a Cite Into the Right Section, Every Time
Read postWhat a Legal Data API Should Guarantee: The Schema Contract
Read post128 Fields on One Statute: What Rich Legal Metadata Actually Buys You
Read post
Co-Founder & CTO
Priyansh leads engineering and AI at Vaquill AI: the pipelines that pull statutes, regulations and court rules from every US jurisdiction's official publisher, and the REST API, MCP server and open dataset that serve them.