Tell a real change from a rotating session token
The Change-Detect API returns website content-monitoring and change-detection data as clean JSON.
5 active endpoints. Every call is 1 credit.
- POST/change-detect/v1/snapshot
- POST/change-detect/v1/diff
- POST/change-detect/v1/monitor_check
- POST/change-detect/v1/extract
- POST/change-detect/v1/batch
What Change-Detect endpoints does ReefAPI ship?
5 live read endpoints. Read-only data API: no writes, no account actions, no dashboard access on the target site.
Change-Detect API
3 of 5 endpoints, ready to run
Three hashes of one page — normalised main content, DOM skeleton and text — plus the title, the extracted content, the word count and the links.
{ "ok": true, "meta": { "api": "change-detect", "endpoint": "snapshot", "mode": "live", "latency_ms": 1023.5, "record_count": 1, "cache_hit": false }, "data": { "content_hash": "feb057ddba5ac313506909af05c17370808a81030f057346499d045c25534dbf", "structural_hash": "512c91013abc5671902f6fa2f0575d0f64927e8bd61885798eb04655a048b68f", "text_hash": "feb057ddba5ac313506909af05c17370808a81030f057346499d045c25534dbf", "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": "2026-08-29T14:12:55Z", "byte_size": 559, "likely_js_app": false, "final_url": "https://example.com", "selector": null, "selector_matched": null } }
How the Change-Detect API works
Change-Detect is a normal ReefAPI surface — the same four rules that hold for every other engine on the key.
No OAuth app, no request signing, no per-site account. One key covers all 184 engines.
Every route is a POST with a JSON body. Parameters are validated against the published schema before anything is charged.
Credits, not seats. Failed and blocked calls are never charged, and cache hits cost nothing.
One envelope everywhere. meta carries latency_ms, record_count and the endpoint that answered.
Watching a competitor's pricing page without a daily false alarm
Naive monitoring hashes the whole response and fires every time a CSRF token, a timestamp or an ad slot rotates. After a week, people stop reading the alerts.
{"url": "https://example.com/pricing", "selector": "#price"}Establishes the baseline. The selector narrows the fingerprint to the part you care about, which removes most of the noise before it starts.
{"url": "https://example.com/pricing", "baseline_hash": "…"}Then on your schedule. Returns changed as a boolean plus the current hashes, so advancing the baseline is one field copy.
The structural hash is the one that answers a different question: whether the page was rebuilt, as opposed to whether the words changed. Both are returned every time.
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"}'{
"ok": true,
"data": { … },
"meta": {
"api": "change-detect",
"endpoint": "snapshot",
"mode": "live",
"latency_ms": …,
"record_count": …
},
"error": null
}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.
How the noise is removed, and what still gets through
Measured on live pages and against a stored baseline.
The content hash is normalised main content with rotating tokens stripped; the structural hash is the DOM skeleton; the text hash is the words. A price change moves the first, a redesign moves the second, and comparing which moved tells you what kind of change you are looking at.
Navigation, headers, footers, cookie banners and comment sections are removed first, so a rotating advertisement or a changing copyright year does not read as a change to the page. This is the difference between an alert people act on and an alert people mute.
Passing a CSS selector fingerprints only that element, and the response reports whether the selector actually matched. That last part matters: a selector that silently stops matching after a redesign would otherwise look like a page that never changes again.
monitor_check answers changed or unchanged and hands back the current hashes in the same response, so moving the baseline forward needs no second call. It also returns the extracted content, meaning an alert can carry what the page now says rather than just the fact that it moved.
A moved hash and a summary of the difference is what you get. Whether a price went up or a clause got worse is a judgement this endpoint does not make. Pair it with the extracted content it returns and decide in your own code, or feed that content to a model.
What people build with Change-Detect
The jobs this data is most often used for.
endpoints
credit per call
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.
What Change-Detect data costs
The cheapest call here is 1 credit, so $15/mo (Pro) buys 10,000 of them — $1.50 per 1,000 credits. Credits roll over and never expire, and failed or blocked calls are not charged.
Full pricing →- 1,000 free credits on signup, no card
- One key, all 184 APIs, one credit pool
- Failed and blocked calls are never charged
- Credits roll over and never expire
Call it in two lines
Sign up, get 1,000 credits and one key that works on every engine. Then this is the whole protocol.
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"])Have a question? We got answers.
The questions people actually ask before wiring up Change-Detect.
Get a free key →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.
18 Developer Tools APIs on the same key
One key, one credit pool, one response envelope. If you are pulling Change-Detect, you are one call away from the rest of the category — no second contract, no second integration.
Need something this API does not do?
Name the endpoint, the field, or a source we do not carry yet. We ship new APIs every week and you would be first to get the key. Real people read every message and reply the same day.
Try it on your own data before you pay anything
The call above is the real endpoint, not a recording. A free key gives you 1,000 credits, the other 183 APIs, and the same envelope everywhere.
Endpoints, parameters and credit costs on this page are read from the live catalog and cannot drift from what the API accepts. Field notes were captured on 2026-08-30.