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.
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"
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.
402 Payment Required. Upgrade to Pro or Business at /pricing.
| Plan | Keys | Requests/minute |
|---|---|---|
| Free | 0 | No access |
| Pro | 1 | 60 req/min |
| Business | 10 | 600 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.
All endpoints are under https://diffwatch-ai.polsia.app/api/v1/. Responses use Content-Type: application/json.
Create a new URL watch. The key's email address is used as the watch owner.
| Field | Type | Required | Description |
|---|---|---|---|
| url | string | Yes | The URL to monitor. |
| selector_type | string | No | full_page (default) | css_selector | text_contains | regex |
| selector_value | string | No | CSS selector, text substring, or regex pattern. Null for full_page. |
| check_interval_minutes | number | No | Check frequency. Clamped to plan minimum (Pro: 5, Business: 5). |
| noise_threshold | number | No | Min 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" } }
List all watches associated with your API key's email. Paginated.
| Query param | Type | Description |
|---|---|---|
| page | number | Page number (default: 1) |
| limit | number | Items 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 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" } } }
Update a watch. All fields are optional — only supplied fields are changed.
| Field | Type | Description |
|---|---|---|
| selector_type | string | New selector type. |
| selector_value | string | New selector value. |
| check_interval_minutes | number | New check interval (clamped to plan minimum). |
| paused | boolean | true 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" }
Stop monitoring a URL. The watch history is preserved but no further checks are run.
// 200 OK { "data": { "id": 1234, "status": "stopped" } }
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 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" } }
All errors follow the same shape:
{ "error": { "code": "error_code", "message": "Human-readable description." } }
| HTTP Status | code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or invalid request parameters. |
| 401 | unauthorized | Missing, invalid, or revoked API key. |
| 402 | upgrade_required | Free plan — API access requires Pro or Business. |
| 404 | not_found | Resource not found or doesn't belong to your account. |
| 409 | watch_limit_reached | Plan watch cap reached. |
| 429 | rate_limit_exceeded | Too many requests. Check Retry-After header. |
| 500 | internal_error | Something went wrong on our end. |
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 →