Skip to content

Change Tracking

New Feature

Detect and diff website changes over time. Compares current page content against previous scrapes with persistent snapshots.

Overview

Add changeTracking to the formats array in any /scrape, /crawl, or /batch/scrape call. Firecrawl stores snapshots persistently (they never expire) and compares automatically.

Change Status Values

StatusMeaning
newFirst time this URL has been scraped
sameContent unchanged since last scrape
changedContent modified (diff available)
removedPage no longer accessible

Basic Usage

python
from firecrawl import Firecrawl

firecrawl = Firecrawl(api_key="fc-YOUR-API-KEY")

result = firecrawl.scrape(
    "https://example.com/pricing",
    formats=["markdown", "changeTracking"]
)

print(result.changeTracking)
# Returns: previousScrapeAt, changeStatus, visibility

Required

markdown format must always be included alongside changeTracking.

Git-Diff Mode

Get a unified diff showing exactly what changed:

python
result = firecrawl.scrape(
    "https://example.com/pricing",
    formats=["markdown", {"type": "changeTracking", "modes": ["git-diff"]}]
)

if result.changeTracking.changeStatus == "changed":
    print(result.changeTracking.diff.text)
    # Unified diff output with +/- lines

The diff response includes both raw text diff and structured JSON with add, del, and normal change types.

JSON Mode (Field-Level Comparison)

Compare specific fields between scrapes using a schema:

python
result = firecrawl.scrape(
    "https://example.com/product/widget",
    formats=["markdown", {
        "type": "changeTracking",
        "modes": ["json"],
        "schema": {
            "type": "object",
            "properties": {
                "price": {"type": "string"},
                "availability": {"type": "string"},
                "rating": {"type": "string"}
            }
        }
    }]
)

# Response shows previous vs current for each field:
# {"price": {"previous": "$19.99", "current": "$24.99"}, ...}

Combined Modes

Use both git-diff and JSON at the same time:

python
result = firecrawl.scrape(
    "https://example.com/pricing",
    formats=["markdown", {
        "type": "changeTracking",
        "modes": ["git-diff", "json"],
        "schema": {
            "type": "object",
            "properties": {
                "plan_name": {"type": "string"},
                "monthly_price": {"type": "string"},
                "features": {"type": "array", "items": {"type": "string"}}
            }
        }
    }]
)

Tags (Separate Tracking Histories)

Track the same URL at different frequencies with isolated histories:

python
# Hourly monitoring (separate history)
result = firecrawl.scrape(
    "https://example.com/pricing",
    formats=["markdown", {"type": "changeTracking", "tag": "hourly"}]
)

# Daily monitoring (separate history)
result = firecrawl.scrape(
    "https://example.com/pricing",
    formats=["markdown", {"type": "changeTracking", "tag": "daily"}]
)

Crawl + Change Tracking

Monitor an entire site for changes:

python
result = firecrawl.crawl(
    "https://example.com",
    limit=50,
    scrape_options={"formats": ["markdown", "changeTracking"]}
)

for page in result.data:
    status = page.changeTracking.changeStatus
    url = page.metadata.url
    if status == "changed":
        print(f"CHANGED: {url}")
    elif status == "new":
        print(f"NEW PAGE: {url}")
    elif status == "removed":
        print(f"REMOVED: {url}")

Batch Scrape + Change Tracking

python
job = firecrawl.batch_scrape(
    [
        "https://example.com/pricing",
        "https://example.com/features",
        "https://example.com/about",
    ],
    formats=["markdown", {"type": "changeTracking", "modes": ["git-diff"]}],
    poll_interval=2,
    wait_timeout=120,
)

Configuration Reference

ParameterTypeDefaultDescription
typestringrequiredMust be "changeTracking"
modesstring[][]"git-diff", "json", or both
schemaobjectnoneJSON Schema for field-level comparison
promptstringnoneCustom LLM prompt (with json mode)
tagstringnullSeparate tracking history identifier

Important Notes

  • markdown format must always be included alongside changeTracking
  • Scoped to your team — first scrape always returns "new"
  • URL matching is exact: source URL + team ID + markdown format + tag
  • Cache is bypassedmaxAge is ignored when using changeTracking
  • Algorithm is resistant to whitespace and content order changes
  • Iframe source URLs are ignored (handles captcha randomization)
  • Snapshots never expire — stored persistently

Cost

  • Basic + git-diff mode: No extra cost (just the normal scrape credit)
  • JSON mode: +5 credits per page

Next: MCP Server →