// developer docs

Diffwatch API

Programmatically create watches, fetch change events, and integrate URL monitoring into your own systems — CI pipelines, internal dashboards, Zapier, or custom tooling. Available on Pro and Business plans.

Contents

Quickstart

Get your API key from /manage → API access panel. Then:

# Create a watch
curl -X POST https://diffwatch-ai.polsia.app/api/v1/watches \
  -H "Authorization: Bearer dw_live_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/pricing","selector_type":"css_selector","selector_value":".price"}'

# List all your watches
curl https://diffwatch-ai.polsia.app/api/v1/watches \
  -H "Authorization: Bearer dw_live_your_key_here"

# Fetch change events for a watch
curl https://diffwatch-ai.polsia.app/api/v1/watches/123/changes \
  -H "Authorization: Bearer dw_live_your_key_here"

Authentication

Every request must include your API key as a Bearer token:

Authorization: Bearer dw_live_<key>

Keys are created from your /manage page under API access. The full key is shown exactly once at creation — copy it immediately. After that, only the prefix is displayed.

Free plan: API access returns 402 Payment Required. Upgrade to Pro or Business at /pricing.

Rate limits

PlanKeysRequests/minute
Free0No access
Pro160 req/min
Business10600 req/min

When you exceed the rate limit, the API returns 429 Too Many Requests with a Retry-After header (seconds). The token bucket refills linearly over the minute.

Endpoints

All endpoints are under https://diffwatch-ai.polsia.app/api/v1/. Responses use Content-Type: application/json.

POST/api/v1/watchesCreate a watch
GET/api/v1/watchesList your watches
GET/api/v1/watches/:idGet a single watch
PATCH/api/v1/watches/:idUpdate selector, interval, pause state
DELETE/api/v1/watches/:idStop a watch
GET/api/v1/watches/:id/changesPaginated change events
GET/api/v1/changes/:idSingle change event

POST /api/v1/watches

Create a new URL watch. The key's email address is used as the watch owner.

FieldTypeRequiredDescription
urlstringYesThe URL to monitor.
selector_typestringNofull_page (default) | css_selector | text_contains | regex
selector_valuestringNoCSS selector, text substring, or regex pattern. Null for full_page.
check_interval_minutesnumberNoCheck frequency. Clamped to plan minimum (Pro: 5, Business: 5).
noise_thresholdnumberNoMin character change to trigger an alert. Default: 50.
// Request
{
  "url": "https://stripe.com/pricing",
  "selector_type": "css_selector",
  "selector_value": ".price",
  "check_interval_minutes": 15
}

// 201 Created
{
  "data": {
    "id": 1234,
    "url": "https://stripe.com/pricing",
    "status": "pending",
    "selector_type": "css_selector",
    "selector_value": ".price",
    "created_at": "2026-06-14T12:00:00.000Z"
  }
}

GET /api/v1/watches

List all watches associated with your API key's email. Paginated.

Query paramTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page, max 100 (default: 20)
// 200 OK
{
  "data": [
    {
      "id": 1234,
      "url": "https://stripe.com/pricing",
      "status": "active",
      "check_interval_minutes": 15,
      "last_checked_at": "2026-06-14T11:45:00.000Z",
      "last_changed_at": "2026-06-10T09:00:00.000Z",
      "change_count": 3,
      "created_at": "2026-06-01T00:00:00.000Z"
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 5, "pages": 1 }
}

GET /api/v1/watches/:id

Get a single watch with its latest snapshot metadata.

// 200 OK
{
  "data": {
    "id": 1234,
    "url": "https://stripe.com/pricing",
    "status": "active",
    "selector_type": "css_selector",
    "selector_value": ".price",
    "check_interval_minutes": 15,
    "last_checked_at": "2026-06-14T11:45:00.000Z",
    "last_changed_at": "2026-06-10T09:00:00.000Z",
    "created_at": "2026-06-01T00:00:00.000Z",
    "latest_snapshot": {
      "id": 5678,
      "content_hash": "sha256hex...",
      "captured_at": "2026-06-14T11:45:00.000Z"
    }
  }
}

PATCH /api/v1/watches/:id

Update a watch. All fields are optional — only supplied fields are changed.

FieldTypeDescription
selector_typestringNew selector type.
selector_valuestringNew selector value.
check_interval_minutesnumberNew check interval (clamped to plan minimum).
pausedbooleantrue to pause, false to resume.
// PATCH body to pause a watch
{ "paused": true }

// PATCH body to update the selector
{ "selector_type": "css_selector", "selector_value": ".pricing-table .price" }

DELETE /api/v1/watches/:id

Stop monitoring a URL. The watch history is preserved but no further checks are run.

// 200 OK
{ "data": { "id": 1234, "status": "stopped" } }

GET /api/v1/watches/:id/changes

Paginated list of detected change events for a watch.

// 200 OK
{
  "data": [
    {
      "id": 9012,
      "detected_at": "2026-06-10T09:00:00.000Z",
      "diff_chars": 142,
      "before_hash": "sha256hex...",
      "after_hash": "sha256hex...",
      "ai_summary": "Pricing plan changed from $9 to $12/mo",
      "permalink": "https://diffwatch-ai.polsia.app/changes/9012"
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 3, "pages": 1 }
}

GET /api/v1/changes/:id

Get a single change event with full before/after content (truncated at 2000 chars).

// 200 OK
{
  "data": {
    "id": 9012,
    "watch_id": 1234,
    "watch_url": "https://stripe.com/pricing",
    "detected_at": "2026-06-10T09:00:00.000Z",
    "diff_chars": 142,
    "before_hash": "sha256hex...",
    "after_hash": "sha256hex...",
    "before_content": "...first 2000 chars...",
    "after_content": "...first 2000 chars...",
    "ai_summary": "Pricing plan changed from $9 to $12/mo",
    "permalink": "https://diffwatch-ai.polsia.app/changes/9012"
  }
}

Errors

All errors follow the same shape:

{ "error": { "code": "error_code", "message": "Human-readable description." } }
HTTP StatuscodeMeaning
400invalid_requestMissing or invalid request parameters.
401unauthorizedMissing, invalid, or revoked API key.
402upgrade_requiredFree plan — API access requires Pro or Business.
404not_foundResource not found or doesn't belong to your account.
409watch_limit_reachedPlan watch cap reached.
429rate_limit_exceededToo many requests. Check Retry-After header.
500internal_errorSomething went wrong on our end.

Webhook payload schema

When Diffwatch detects a change it POSTs a JSON payload to every enabled webhook channel for that watch. See the full schema, signing, and retry behavior in the webhook docs →

Webhook docs → Pricing → Start watching →