Change-Detect API & Scraper
The Change-Detect API returns website content-monitoring and change-detection data as clean JSON.
🤖 Using an AI assistant? Copy this link into ChatGPT / Claude / Cursor — it reads every endpoint and parameter instantly and tells you if this API fits your use case.
The primary snapshot endpoint returns a page's content hash, structural hash, text hash, title, meta description, main content, normalized text, word count and links — a fingerprint you can compare over time. You can also diff two snapshots, run a monitor_check, extract structured content and batch multiple URLs. It is built for change monitoring, compliance and competitive-intelligence workflows that need to detect when a page changes without building a diffing pipeline. One ReefAPI key, one shared credit pool, the standard envelope.
Which value to store, and what each mode compares
This engine keeps nothing between calls. You store the fingerprint and hand it back, so picking the right field to store is the whole design decision. The hashes and counts below were measured against example.com and iana.org on 2026-08-27.
| Field or parameter | Behavior | Measured |
|---|---|---|
| data.content_hash | 64 hex characters. This is the value to store and pass back as baseline_hash | example.com = feb057ddba5ac313506909af05c17370808a81030f057346499d045c25534dbf |
| meta.content_hash | The first 16 characters only, a short echo for logs. Do not store this one | feb057ddba5ac313 |
| text_hash | Identical to content_hash when no selector is set | Both fields matched character for character on example.com |
| structural_hash | DOM tag skeleton. Holds still when only wording moves | Across two entirely different pages, structural_changed stayed false while the text diff read 100% |
| baseline as an object or raw text | You get added[], removed[], modified[] and change_ratio | 13 blocks removed, 3 modified, change_ratio 1.0 |
| baseline as a bare 64-hex hash | changed is still answered, but the block lists come back empty and change_ratio null | summary says so verbatim: textual diff unavailable, baseline was a hash, not full content |
| selector | Full CSS or XPath, both accepted in the same parameter | 'h1' and '//h1' on the same page produced the identical hash 8d7e39b434a6bd30... and word_count 2 |
| selector with no match | Falls back to the whole page and says so, it does not error | selector_matched false, word_count back at the full-page value, ok:true |
| mode=links | Compares the outbound link set only. change_ratio is null in this mode | added_count 1, removed_count 42 between example.com and an IANA page |
| batch | Up to 20 URLs, each guarded independently, partial failure allowed | 3 URLs, ok_count 2, the third an inline NOT_FOUND for a host that does not resolve |
Private, internal and cloud-metadata targets are refused. A snapshot of http://169.254.169.254/latest/meta-data/ returned INVALID_PARAM with detail.ssrf_blocked true and the resolved address echoed back. Every redirect hop is validated the same way, so a public host that redirects inward is stopped mid-chain.
Real request and response JSON
Captured from the indexed primary action, snapshot, on .
{
"method": "POST",
"url": "https://api.reefapi.com/change-detect/v1/snapshot",
"headers": {
"x-api-key": "$REEF_KEY",
"content-type": "application/json"
},
"body": {
"url": "https://example.com"
}
}{
"ok": true,
"meta": {
"api": "change-detect",
"endpoint": "snapshot",
"mode": "live",
"latency_ms": 679.4,
"record_count": 1,
"bytes": 559,
"cache_hit": false,
"method": "fetch_normalize_fingerprint_bare_dc_curl_cffi",
"ssrf_guarded": true,
"noise_resistant": true,
"final_url": "https://example.com",
"content_hash": "feb057ddba5ac313",
"likely_js_app": false
},
"data": {
"content_hash": "feb057ddba5ac[redacted-phone]af05c17370808a81030f[redacted-phone]d045c25534dbf",
"structural_hash": "512c91013abc5671902f6fa2f0575d0f64927e8bd61885798eb04655a048b68f",
"text_hash": "feb057ddba5ac[redacted-phone]af05c17370808a81030f[redacted-phone]d045c25534dbf",
"title": "Example Domain",
"meta_description": null,
"main_content": "Example Domain\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\nLearn more",
"normalized_text": "Example Domain\nThis domain is for use in documentation examples without needing permission. Avoid use in operations.\nLearn more",
"word_count": 19,
"links": [
"https://iana.org/domains/example"
],
"link_count": 1,
"content_type": "text/html",
"status": 206,
"fetched_at": "[redacted-phone]T17:19:28Z",
"byte_size": 559,
"likely_js_app": false,
"final_url": "https://example.com",
"selector": null,
"selector_matched": null
}
}What the Change-Detect API does
| Action | Description | Concrete use case | Key params |
|---|---|---|---|
| snapshot | Fetch a URL and return a noise-resistant fingerprint: content_hash (normalised main content, rotating-token-stripped), structural_hash (DOM skeleton), text_hash, title, meta_description, main_content (boilerplate-stripped), word_count, links[]. Store the returned snapshot/hash and feed it back to diff/monitor_check later. | Platform and DevOps teams call snapshot to fetch a URL and. | url, selector, follow_redirects |
| diff | Diff a page against a prior baseline (snapshot object OR content_hash OR raw text), or compare two live URLs (url + url2). Returns changed(bool), change_ratio(0..1), change_type(text|structural|both|none), added[]/removed[]/modified[] blocks, and a human summary — all on the NOISE-RESISTANT normalised content. | Security and supply-chain teams call diff to get diff a page against a prior baseline (snapshot object OR content_hash OR raw text), or compar…. | url, baseline, url2, selector, mode, ... |
| monitor_check | The stateless half of monitoring: re-fetch the URL and tell you FAST whether it changed since a known hash. Pass baseline_hash (+ optional selector/mode). Returns changed(bool) + current_hash + a short diff_summary — the cheap 'did it change?' path. | Developer-tool builders call monitor_check to get the stateless half of monitoring. | url, baseline_hash, selector, mode, follow_redirects |
| extract | Main-content extraction only — boilerplate (nav/header/footer/ads/cookie/comments) stripped → clean text + structured blocks + links. Useful to feed clean diffs or to get just the article. No fingerprint comparison. | AI-agent developers call extract to get main-content extraction only. | url, selector, follow_redirects |
| batch | Snapshot up to 20 URLs concurrently in one call (each independently SSRF-guarded, shared concurrency). Returns a compact fingerprint per URL. | Platform and DevOps teams call batch to get snapshot up to 20 URLs concurrently in one call (each independently SSRF-guarded, shared conc…. | urls, selector |
Call snapshot from your stack
curl -X POST https://api.reefapi.com/change-detect/v1/snapshot \
-H "x-api-key: $REEF_KEY" \
-H "content-type: application/json" \
-d '{"url":"https://example.com"}'import requests
r = requests.post(
"https://api.reefapi.com/change-detect/v1/snapshot",
headers={"x-api-key": REEF_KEY},
json={
"url": "https://example.com"
},
)
print(r.json()["data"])const res = await fetch("https://api.reefapi.com/change-detect/v1/snapshot", {
method: "POST",
headers: {
"x-api-key": process.env.REEF_KEY,
"content-type": "application/json",
},
body: JSON.stringify({
"url": "https://example.com"
}),
});
const { ok, data, meta, error } = await res.json();Ask your MCP-connected assistant: call reefapi.change-detect.snapshot with {"url":"https://example.com"}.Who uses this API and why
- Competitive-intelligence tools call snapshot then diff to detect when a competitor changes pricing or copy.
- Compliance teams use monitor_check to alert when a terms or policy page is updated.
- Content pipelines use extract and batch to normalize and track many pages at once.
Questions developers ask before integrating
Where is my baseline stored?
Nowhere. Every action is a single fetch: you get a fingerprint back, you store it, and you pass it in next time. That is the trade. There is no watch to create, no schedule to pay for and no per-monitor cap, and equally no history you can ask us for later. Scheduling belongs in your cron or queue; this engine is the compare step.
Can I feed a hash straight from snapshot back into monitor_check?
Yes, and it round-trips cleanly. Taking content_hash feb057dd... from a snapshot of example.com and passing it as baseline_hash to monitor_check returned changed:false, hash_kind 'content_hash' and diff_summary 'Unchanged since baseline (hash matches).' The engine works out for itself whether the 64-hex value you sent is a content_hash or a text_hash and reports which one it matched in hash_kind, so you do not have to track that separately.
Why are added[] and removed[] empty when I pass a hash?
Because a hash carries no text to diff against. baseline_kind tells you which path you took. 'hash' gives you changed, current_hash and a summary, with change_ratio null and change_type 'unknown'. 'url2', a full snapshot object or raw prior text gives you the block lists. If you want the wording that moved, store normalized_text or the whole snapshot object next to the hash and pass the object as baseline.
What does selector_matched: false mean, and is it an error?
It means your CSS selector or XPath matched nothing, so the whole page was fingerprinted instead, and the call still returns ok:true. That is the dangerous case for a price watch: your selector silently widened to the entire page, and from then on every unrelated edit reads as a change. Assert on selector_matched being true in your own code before trusting a scoped hash.
Does the selector accept XPath as well as CSS?
Both, in the same parameter, told apart by shape. On the IANA example-domains page, selector 'h1' and selector '//h1' produced the identical content_hash and the identical word_count of 2. Use whichever expresses the region better. XPath is usually the shorter route to a specific table cell, for example //table//tr[2]/td[3].
status came back 206 instead of 200. Is my content truncated?
Not necessarily. status is the upstream HTTP status reported verbatim, and example.com answered 206 while returning its complete 559-byte body, the correct title, 19 words and its single outbound link. Treat any 2xx as a good fetch and judge completeness from byte_size and word_count rather than from the status code alone.
What makes the diff noise-resistant?
Comparison runs on extracted main content rather than raw HTML: navigation, header, footer, ads, cookie banners and comment blocks are stripped, and rotating tokens are normalized away before hashing. The practical proof is that the same page fetched twice produced the identical content_hash despite a new fetched_at each time. If you want the cleaned text without any comparison, `extract` returns it with a blocks[] array (16 blocks, 120 words on the IANA page) and no fingerprint work.
What happens to one bad URL in a batch of twenty?
It fails on its own and the rest still come back. A three-URL batch containing a hostname that does not resolve returned ok:true at the top level with count 3 and ok_count 2, while the failing row carried an inline error object with code NOT_FOUND and the message that the host does not resolve. Read ok_count, not the top-level ok, whenever you batch.
What is the Change-Detect API?
Change-Detect API is a ReefAPI endpoint group for change-detect It returns live JSON through POST requests under /change-detect/v1.
Is the Change-Detect API free to try?
Yes. ReefAPI starts with 1,000 free credits, no card required. Change-Detect calls use the same shared credit balance as every other ReefAPI engine.
Do I need a Change-Detect login or account?
No login to Change-Detect is needed for the API response. You call ReefAPI with your x-api-key header, and the playground can run live examples before you create a production key.
How fresh is the Change-Detect data?
The page example is captured from a live snapshot call, and production requests fetch live data through ReefAPI rather than a static sample.
How many credits does the Change-Detect API use?
Change-Detect actions currently cost 1 credit per successful call. Failed or blocked calls are free, and all APIs draw from one credit pool.
Can I call Change-Detect from an AI assistant or MCP client?
Yes. Connect ReefAPI once through MCP and your assistant can call change-detect actions with the same key, credit pool and JSON envelope used by normal REST requests.