curl --request GET \
--url https://api.vaquill.ai/api/v1/watches/{watch_id}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/watches/{watch_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/watches/{watch_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/watches/{watch_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))
}{
"data": {
"changes": [
{
"id": 91,
"refreshLogId": 4412,
"corpusType": "cfr",
"changeKind": "amended",
"actId": "CFR_T21_P314_S314_50",
"citation": "21 CFR 314.50",
"title": "Content and format of an NDA",
"detectedAt": "2026-08-07T04:10:00Z",
"sourceChangedOn": "2026-08-05",
"hasDiff": false
}
]
},
"meta": {
"processingTimeMs": 12,
"creditsConsumed": 0,
"cursor": 91,
"hasMore": false
}
}{
"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": "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": [
{
"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"
}
]
}List what changed on a watch's source
The sections this watch’s source added, amended, or removed, newest first. This is the per-item detail behind the counts in a notification: not “3 sections changed” but which three.
Covers the source’s whole captured history, not just the period since you subscribed, so a watch created today can still read back what the source did before it. For the narrower ‘what has my alert actually covered’ view, pass since= the watch’s own createdAt.
History is bounded by capture, not by the age of the law: events exist only from when change capture began for that source, and are swept at 5 years. An empty list means no captured change, never ‘never amended’ — the publisher’s own history, where there is one, is on the section’s amendmentHistory.
Safe to poll alongside a webhook or email watch. This endpoint is read-only and there is no server-side ‘last checked’ state to interfere with: notifications fire when a corpus refresh completes, not by comparing against a watermark, and nothing here writes one. Polling cannot suppress, advance, or double-fire a delivery. (lastNotifiedAt on the watch records the outcome of the last delivery attempt for your visibility; it is never an input.)
Paging. Pass sinceId with meta.cursor from your previous page to get only what is new. meta.hasMore is true when the page filled to limit, so keep calling with the new cursor until it is false. Use order=asc while catching up.
changeKind is added (a new section), amended (its content was replaced), or removed (it disappeared from a full refresh of the source). citation and title are null for corpora that do not carry them.
detectedAt is when OUR refresh saw the change. sourceChangedOn, the publisher’s own date for it, is a much narrower field: we carry it on cfr and on no other board, so on everything else it is absent from every event. That absence is about our capture, not about the publisher, and it never licenses reading detectedAt as the amendment date. Check publishesSourceChangedOn on GET /boards to know which case a board is in.
Free.
curl --request GET \
--url https://api.vaquill.ai/api/v1/watches/{watch_id}/changes \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.vaquill.ai/api/v1/watches/{watch_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/watches/{watch_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/watches/{watch_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))
}{
"data": {
"changes": [
{
"id": 91,
"refreshLogId": 4412,
"corpusType": "cfr",
"changeKind": "amended",
"actId": "CFR_T21_P314_S314_50",
"citation": "21 CFR 314.50",
"title": "Content and format of an NDA",
"detectedAt": "2026-08-07T04:10:00Z",
"sourceChangedOn": "2026-08-05",
"hasDiff": false
}
]
},
"meta": {
"processingTimeMs": 12,
"creditsConsumed": 0,
"cursor": 91,
"hasMore": false
}
}{
"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": "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": [
{
"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
Query Parameters
Max changes to return.
1 <= x <= 200Return only changes with an id greater than this. The cursor to build on: ids are a monotonic sequence, so this is exact, immune to clock skew, and cannot drop two changes that share a timestamp. Carry meta.cursor forward from your last page.
x >= 0Return only changes with an id less than this: the cursor for walking BACK through history, where sinceId walks forward into new changes. A newest-first reader needs this one, since paging down a descending list means asking for what sits below the lowest id already held. Pass the smallest id on your last page.
x >= 0Return only changes detected strictly after this ISO-8601 timestamp. Coarser than sinceId (a single refresh writes many rows at the same instant, and this drops all of them), but useful when all you kept was the detectedAt off a changes[] item in a webhook body. Prefer that item's id as sinceId where you still have it. Both may be combined.
"2026-08-07T04:10:00Z"
Filter to these kinds. Repeat the parameter to pass several (?changeKind=added&changeKind=amended). Omit for all three.
added, amended, removed desc (default) is newest first, for showing a recent-activity list. Use asc when catching up from a cursor: walking forward means a page that hits limit leaves the gap at a known end.
asc, desc Response
What changed, newest first.
Was this page helpful?

