curl --request GET \
--url https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"actId": "CFR_T21_P314_S314_50",
"section": {
"actId": "CFR_T21_P314_S314_50",
"citation": "21 C.F.R. § 314.50",
"title": "Content and format of an NDA",
"corpusType": "CFR",
"state": "federal"
},
"changes": [
{
"id": 91,
"changeKind": "amended",
"detectedAt": "2026-08-07T04:10:00Z",
"citation": "21 CFR 314.50",
"displayCitation": "21 C.F.R. § 314.50",
"title": "Content and format of an NDA",
"hasDiff": true,
"corpusType": "cfr"
}
],
"total": 1,
"hasMore": false,
"cursor": 91,
"coverage": "Observed changes only: what a refresh of this source detected, from when change capture began for it through today, retained 5 years. This is not the section's legislative history, and an empty list means no captured change rather than never amended.",
"processingTimeMs": 18.4,
"creditsConsumed": 1
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}{
"detail": "Section not found. This id does not exist, but the section does, ...",
"actId": "<string>",
"reason": "assembled_id",
"didYouMean": [
"USC_T26_C1_S1"
]
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}Get this section's change history
Every change our refreshes have observed to one section: when it was added, each time its text was replaced, and whether it has since been removed.
Cost: 1 credit. Not-found and failed lookups are not charged. An empty history for a section that exists IS the answer and is charged.
A section lookup tells you what the law says today. This tells you whether that is the same thing it said when you last reviewed it. Unlike a board watch, which can only show changes that postdate the subscription, this reads the history already captured.
What this is, and is not. These are OBSERVED changes: a refresh compared
the source against the copy we held and found it different. detectedAt is
when we saw it, an upper bound on when it took effect, never the effective date
itself. For the publisher’s own effective and amendment dates, read
amendmentHistory on GET /us/statutes/section/{act_id}. The two are
complements: the publisher tells you what it says changed, this tells you what
actually moved and when we could have told you.
Coverage is bounded by capture, not by the age of the law. Change capture
began long after the corpus itself did, it is per-source, and events are swept
at 5 years. An empty changes list therefore means no captured change, not
never amended. The coverage field on every response says so; surface it
rather than rendering an empty list as “unchanged”.
A removed section still answers. When the newest change is a removed, the
section is gone from the corpus and section comes back null. That is a
successful, charged response, not a 404 — learning that a provision was
repealed is the point.
Paging. hasMore is true when the page filled to limit. Walk backwards
through history with beforeId (the smallest id you hold) and forward into
new changes with sinceId (cursor from your last page).
Alerting on future changes is a different surface: subscribe to the
corpusType on this response via POST /boards/watches, optionally scoped to
this exact actId, and take before/after diff text from the watch’s own diff
endpoint.
curl --request GET \
--url https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.vaquill.ai/api/v1/us/statutes/section/{act_id}/changes"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"actId": "CFR_T21_P314_S314_50",
"section": {
"actId": "CFR_T21_P314_S314_50",
"citation": "21 C.F.R. § 314.50",
"title": "Content and format of an NDA",
"corpusType": "CFR",
"state": "federal"
},
"changes": [
{
"id": 91,
"changeKind": "amended",
"detectedAt": "2026-08-07T04:10:00Z",
"citation": "21 CFR 314.50",
"displayCitation": "21 C.F.R. § 314.50",
"title": "Content and format of an NDA",
"hasDiff": true,
"corpusType": "cfr"
}
],
"total": 1,
"hasMore": false,
"cursor": 91,
"coverage": "Observed changes only: what a refresh of this source detected, from when change capture began for it through today, retained 5 years. This is not the section's legislative history, and an empty list means no captured change rather than never amended.",
"processingTimeMs": 18.4,
"creditsConsumed": 1
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}{
"detail": "Section not found. This id does not exist, but the section does, ...",
"actId": "<string>",
"reason": "assembled_id",
"didYouMean": [
"USC_T26_C1_S1"
]
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}{
"detail": "Insufficient API credits.",
"errors": [
{
"loc": [
"body",
"corpusType"
],
"msg": "Input should be 'USC', 'CFR' or 'STATE'",
"type": "literal_error"
}
]
}Authorizations
API key issued from the developer dashboard. Pass as Authorization: Bearer vq_key_... (preferred).
Path Parameters
Section identifier, e.g. USC_T42_C21_S1983 (Title 42, Chapter 21, Section 1983, which a lawyer writes as 42 U.S.C. § 1983). Take it from a /us/statutes/search or /us/statutes/resolve result.
A citation also works here: 26 U.S.C. § 1, 42 USC 1983 or Cal. Civ. Code § 1950.5 is resolved with the same resolver /resolve uses, and the section is served. A citation costs this endpoint's price PLUS the /resolve price (2 credits), charged as its own line whether or not it resolves, exactly as /resolve charges; that is the same total as calling /resolve and then this endpoint, in one round trip. An exact act_id costs only this endpoint's price. When a citation resolves, or when the id only matched after surrounding whitespace, quotes or a trailing period were removed, the response carries resolvedFrom saying what your input was matched as. Check it the way you would check a /resolve answer. A citation containing / cannot travel in a URL path segment; resolve it with GET /us/statutes/resolve instead.
Do not assemble an id from a citation: the title and section are derivable, but the CHAPTER is not, so USC_T26_S1 misses. A miss returns 404 with reason and, where the section exists under another id, didYouMean.
State session laws (acts as enacted, ids starting SSL_) are not code sections: read them at /us/session-laws/{sessionLawId}.
3 - 200"CFR_T21_P314_S314_50"
Query Parameters
Max changes to return on this page.
1 <= x <= 20050
Return only changes with an id greater than this: the cursor for polling forward into new changes. Ids are a monotonic sequence, so this is exact, immune to clock skew, and cannot drop two changes that share a timestamp. Carry cursor forward.
x >= 0Return only changes with an id less than this: the cursor for walking BACK through history, which is what a newest-first reader needs. Pass the smallest id on your last page.
x >= 0Filter to these kinds. Repeat the parameter to pass several (?changeKind=amended&changeKind=removed). Omit for all three.
added, amended, removed desc (default) is newest first, the natural reading order for a history. Use asc to replay a section's life forward, or when catching up from a cursor.
asc, desc Response
The section's captured change history, newest first.
Response for GET /us/statutes/section/{act_id}/changes.
The act_id of the section served. Equal to your request unless resolvedFrom is set, in which case it is the id your citation or cleaned-up input resolved to.
"CFR_T21_P314_S314_50"
What this history can and cannot tell you, in one sentence. Change capture began well after the corpus itself did and is swept on a retention window, so this endpoint reports the changes we OBSERVED in that window, never the section's full legislative history. Read it before treating an empty list as 'unchanged'.
"Observed changes only, from when capture began for this source through today, retained 24 months. Not a full legislative history."
The section as it stands today. NULL is meaningful and not an error: it means the section is no longer in the corpus, which is exactly the case when the newest change is a removed. The history below is still the answer.
Show child attributes
Show child attributes
Observed changes, newest first by default. Empty is a real answer, and it means NO CAPTURED CHANGE, not that the section has never been amended. See coverage.
Show child attributes
Show child attributes
Size of changes on this page, not a corpus-wide count: it is capped by limit.
3
True when the page filled to limit, so more may sit behind it. Page with beforeId (walking back) or sinceId (walking forward) rather than by raising limit.
Highest id on this page, or null when the page is empty. Carry it into sinceId to poll for what is new; keep the one you hold when a page comes back empty.
91
The earliest change we ever captured for this section: the OBSERVATION HORIZON this history sits on. Read it before treating an empty or short list as the section's full history.
Null means we have never captured a change for this section, so an empty changes list is not evidence that it is stable. A non-null value with an empty list means your filters or cursor excluded everything we hold, which is a different answer.
coverage states the same caveat in prose. This is the fact behind it, and it is the same field, meaning the same thing, as asOf.observedFrom on GET /section/{actId}/body.
"2026-08-14T04:10:00Z"
Null when the section is exactly the act_id you sent. Otherwise how your input was matched: a citation, or an act_id with transport damage (whitespace, quotes, a trailing period) removed.
Show child attributes
Show child attributes
Server-side time for this request in milliseconds, excluding network transit. Useful for spotting a slow query; not billed on.
18.4
Credits actually charged for this call. Read it rather than assuming the list price: failed and refunded work bills 0.
1
Was this page helpful?

