track_changes
Store a snapshot of a page, then compare the live page against it whenever you want. You get back whether anything changed, how much, which lines were added or removed, and a structural similarity score telling you whether the markup itself was rebuilt.
Use Cases
Detect a scraper about to break
A low structural_similarity means the page's markup was rebuilt, which is what actually breaks selectors — catch it before your extraction pipeline starts returning empty results.
Watch competitor pricing pages
Scope to the pricing table with selector, compare on your own schedule, and read the added and removed lines to see what moved.
Track legal and policy documents
Compare Terms of Service, privacy policies, or regulatory pages and keep an auditable record of exactly which lines changed.
Catch breaking changes in API docs
Baseline a vendor's reference or changelog page and compare before each of your releases.
Verify a deploy changed only what you expected
Baseline a page before shipping, compare after, and confirm the diff matches the intended change.
Endpoint
/api/v1/tools/track_changesParameters
trackingOptions, monitoringOptions and storageOptions objects — the hosted REST API accepts those keys and ignores them, so sending them changes nothing. The equivalent controls exist on the CrawlForge MCP server.| Name | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | Required | - | The page to capture or compare. Example: https://competitor.com/pricing |
operation | string | Optional | compare | Either `"create_baseline"` or `"compare"`. Passing `"monitor"` returns 501 — scheduled monitoring is an MCP-server feature. Any other value is rejected with 400. Example: compare |
selector | string | Optional | - | CSS selector scoping tracking to part of the page, e.g. `.pricing-table`. Baselines are stored per (url, selector) pair, so the same URL can be tracked at several scopes independently. Returns 422 if the selector matches nothing. Example: .pricing-table |
update_baseline | boolean | Optional | false | `compare` only. After diffing, overwrite the stored baseline with the content just fetched. Use this for rolling comparison, where each call reports the change since the previous call rather than since the original capture. Example: false |
respect_robots | boolean | Optional | true | Respect the target site's robots.txt. Left at `true`, a path disallowed for `CrawlForge` is refused with 403 before anything is fetched and no credits are charged. Set it to `false` only for a target you have your own agreement with — the response then carries a `warnings` entry and the override is recorded against your API key. Example: true |
Operations
Create a baseline once, then compare against it as often as you like.
Reading a comparison
The two scores answer different questions, and the combination is more useful than either alone.
structural_similarity is null, not 0, when the stored baseline predates the field. Zero is a real score meaning nothing structural survived, so an unmeasured comparison reports null instead. Re-run create_baseline, or pass update_baseline once, to start scoring it.Request Examples
# Step 1: capture the baseline (once per url + selector, kept 90 days)
curl -X POST https://crawlforge.dev/api/v1/tools/track_changes \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://competitor.com/pricing",
"operation": "create_baseline",
"selector": ".pricing-table"
}'
# Step 2: compare against it, as often as you like
curl -X POST https://crawlforge.dev/api/v1/tools/track_changes \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://competitor.com/pricing",
"operation": "compare",
"selector": ".pricing-table"
}'
# Rolling comparison: diff against the previous call, not the original capture
curl -X POST https://crawlforge.dev/api/v1/tools/track_changes \
-H "X-API-Key: cf_test_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://competitor.com/pricing",
"operation": "compare",
"selector": ".pricing-table",
"update_baseline": true
}'Response Example
{ "success": true, "data": { "operation": "compare", "url": "https://competitor.com/pricing", "selector": ".pricing-table", "changed": true, "change_percent": 12.5, "structural_similarity": 0.9713, "added_count": 4, "removed_count": 2, "added_samples": [ "Pro $79 / month", "Includes 50,000 credits", "Enterprise", "Contact sales" ], "removed_samples": [ "Pro $99 / month", "Includes 25,000 credits" ], "baseline_captured_at": "2026-08-01T12:00:00.000Z", "compared_at": "2026-08-26T14:30:00.000Z", "baseline_updated": false }, "credits_used": 3, "credits_remaining": 997, "processing_time": 1180}data.changedTrue when the content hash differs from the baseline — the fastest check if you only need a yes or no.data.change_percentAdded plus removed lines as a percentage of the larger document, 0-100.data.structural_similarity0-1 markup similarity. Null when the baseline predates this field.data.added_samplesUp to 20 added lines, each truncated to 500 characters. Not the full diff — use added_count for the true total.data.removed_samplesUp to 20 removed lines, same caps as added_samples.data.baseline_updatedWhether this call overwrote the baseline, echoing the update_baseline parameter.credits_used3 credits, charged per successful call. Failed calls are not charged.Error Handling
No baseline found (404 BASELINE_NOT_FOUND)
You called compare before storing a baseline, or the 90-day baseline expired. Run create_baseline for this exact (url, selector) pair first.
Selector matched nothing (422 SELECTOR_NOT_FOUND)
The CSS selector returned no elements. Check it against the live markup — a selector that works in devtools after JavaScript runs may not exist in the raw HTML this endpoint fetches.
Target unreachable (502 FETCH_FAILED)
The page returned a non-2xx status. Sites behind bot protection commonly land here — fetch them with stealth_mode instead.
Scheduled monitoring unavailable (501 OPERATION_NOT_AVAILABLE)
operation: "monitor" is not implemented on the hosted REST API. Call compare on your own schedule, or use the CrawlForge MCP server.
Invalid parameters (400 VALIDATION_ERROR)
Malformed URL, or an operation outside create_baseline, compare and monitor. The response details array names the failing field.
Storage unavailable (503 STORAGE_UNAVAILABLE)
Baseline storage could not be reached. Retry — no credits are charged for failed calls.
Blocked by robots.txt (403 Forbidden)
The target site's robots.txt disallows this path for CrawlForge. Set respect_robots: false to override if you have your own agreement with the target — the override is recorded against your API key. The override does not reach a host on CrawlForge's permanent opt-out list, which is refused whatever respect_robots is set to.
selector rather than tracking whole pages. A full-page baseline picks up nav, footer, cookie banners and rotating content, which is usually what produces a change on every single comparison.Credit Cost
create_baseline and compare cost 3 credits. Failed calls are not charged. There is no scheduler on the REST API, so your comparison frequency — and therefore your cost — is entirely under your control.Cost Breakdown:
create_baseline: 3 credits, once per (url, selector) pair, valid 90 days
compare: 3 credits per call
Polling Cost Example, per URL:
Hourly: 24 calls/day = 72 credits/day
Every 6 hours: 4 calls/day = 12 credits/day
Daily: 1 call/day = 3 credits/day
Plan Recommendations:
Free Plan: 1,000 one-time trial credits = about 5 URLs compared every 6 hours for a month
Hobby Plan: 5,000 credits/mo = about 13 URLs compared every 6 hours ($19/mo)
Professional Plan: 50,000 credits/mo = about 138 URLs compared every 6 hours ($99/mo)