Watch

Watch a web page, a part of it, a JSON value or a price for weeks. Get the changes by polling, signed webhook or email. REST and MCP on one base URL: https://watch.darmousseh.com

Quick start

curl -H "Authorization: Bearer $KEY" https://watch.darmousseh.com/v1/ping
curl -X POST -H "Authorization: Bearer $KEY" -H "Idempotency-Key: demo-1" -H "content-type: application/json" \
  -d '{"url":"https://example.com/product","selector":".price","condition":"below","threshold":50,"interval_min":60}' https://watch.darmousseh.com/v1/watches
curl -H "Authorization: Bearer $KEY" "https://watch.darmousseh.com/v1/changes?since=0"

Keys are long-lived Bearer tokens issued by the operator and revocable at any time. Requests never redirect.

What a watch can look at

FieldMeaning
urlPublic http(s) page or JSON endpoint (required). No localhost, IPs or internal names.
selectorCSS selector: compare only that part of an HTML page.
json_pathValue inside a JSON response, e.g. data.items[0].price. Use this for public-records APIs.
keywordAlert when the text appears or disappears (case-insensitive).
condition + thresholdNumber alert: below/above. Fires when the first number found crosses the threshold (and again after it clears and recrosses).
regexNarrow the extracted text before comparing (first capture group).
interval_min5-10080; free plan minimum 60. Checks run from a 5-minute scheduler, so allow up to 5 minutes of slack.
webhook_urlhttps endpoint. POST JSON, header X-Watch-Signature: t=UNIX,v1=HEX where HEX = HMAC-SHA256(webhook_secret, t + "." + body). 2xx = delivered; otherwise retried after 1m, 5m, 30m, 2h, 6h, then marked failed.
emailAddress that gets one email per change from vsm-bot@agentmail.to (automated).
nameLabel used in change summaries.

With no selector or json_path the whole visible page text is compared, so pages with clocks or rotating ads look "changed" every time; use a selector. The first check happens when you create the watch and sets the baseline (no change is recorded, except a threshold that is already met).

REST

CallDoes
GET /v1/pingHarmless first read: key id, plan, limits.
POST /v1/watchesCreate a watch. Header Idempotency-Key makes retries safe (24h). 201 with the watch (including webhook_secret) and the baseline reading.
GET /v1/watches?limit=10&cursor=Your watches, newest first; next_cursor pages on.
GET /v1/watches/{id}One watch with last value, status and last error.
DELETE /v1/watches/{id}Stop a watch. Its changes stay readable.
GET /v1/changes?since=CURSOR&watch_id=&limit=10With since: changes after that cursor, oldest first (poll loop: pass back the returned cursor). Without: the 10 newest; page back with before=older_cursor.

Errors

Always JSON: {"error":{"code","message","retry"}}.

StatusCodeRetry?
400invalid_request, invalid_selectorno, fix the input
401unauthorizedno
403key_revoked, plan_limitno
404not_found (unknown watch or route)no
405method_not_allowedno
429rate_limited with Retry-After secondsyes, after the delay
500internalyes, with the same Idempotency-Key

MCP

Streamable HTTP endpoint https://watch.darmousseh.com/mcp, same Bearer key. Tools: ping, create-watch, list-watches, get-watch, delete-watch, list-changes. Results are JSON text; errors set isError and carry the same error object. Writes accept idempotency_key. Call ping first.

Plans

FreePro (not on sale yet)
Watches5100
Min interval60 min5 min
Requests30/min, 2,000/day120/min, 50,000/day
Emails10/day200/day
Price$0$12/month planned

Fetching, honestly

Pages are fetched from Cloudflare's network as WatchBot/1.0 with no JavaScript rendering. Pages that need JavaScript to show their content, or sites that block datacenter traffic, show up on the watch as last_error (for example HTTP 403 (site may be blocking automated fetches)); three failures in a row are recorded as a change of kind error. Change kinds: content, keyword_appeared, keyword_gone, threshold, threshold_cleared, error.