Change-detection alerts, billed on a real self-custody rail.
Watchbot watches a URL, fingerprints it every cycle, and webhooks you the instant content changes. Metered at 1 credit per check (1 credit = 1¢), paid up-front with real USDC / USDT / native ETH on Base or Ethereum. No account on the earning side — bearer keys, hashed at rest.
Endpoints
01Quickstart — watching your first page
Every call is https://<host>/… — same origin on the public storefront, no CORS, no SDK. A key is minted open (no account), then everything after it is yours alone.
# 1 · mint an API key — shown once, hashed at rest
curl -X POST https://<host>/keys \
-H 'content-type: application/json' \
-d '{"name":"my-agent","start_credits":0}'
# → { "customer_id":3, "api_key":"4f2e…(48 hex)", "credits":0 }
# 2 · watch a URL, webhook on change
curl -X POST https://<host>/watch \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"url":"https://example.com","webhook_url":"https://myapp.app/hooks/watchbot"}'
# → { "watched":true, "id":12, "url":"https://example.com", "webhook_url":"…" }
# 3 · buy credits → real invoice (USDC/USDT/ETH)
curl -X POST https://<host>/purchase \
-H "authorization: Bearer $KEY" -H 'content-type: application/json' \
-d '{"credits":900}'
# → { "order_id":19, "asset":"USDC", "min_usdc":"9.000000", "recipient":"0xFCEf…b50e", "accept_on":[…], "expires_at":"…" }
# 4 · the moment payment confirms, Watchbot credits you and calls your webhook
curl https://<host>/orders -H "authorization: Bearer $KEY"
# → paid orders flip to "paid" automatically02Authentication
Register via POST /keys (open — no account, no KYC on the earning side). The bearer key is returned once, then only its SHA-256 hash is stored — it cannot be re-read or reset. Send it as Authorization: Bearer <key>.
| Scenario | Status | Body |
|---|---|---|
| Missing or bad token | 401 | {"error":"missing/invalid bearer token — register via POST /keys"} |
| Public/open endpoints first | — | /health, /ticker, POST /keys need no token |
03Endpoints
Liveness + occupancy. Poll this to prove the agent is up.
{
"ok": true,
"uptime_s": 598,
"targets": 1, "customers": 3,
"poll_interval_ms": 60000, // target poll cadence
"price_per_check": 1 // credits per check
}Public earnings. Revenue is settled value only — what actually landed and was credited. Never projected.
{
"product": "Watchbot", "rail": "live",
"settlements": 1,
"revenue_usd": 6.78, // settled value, $1 → 100 credits
"credits_issued": 678,
"first_paid_at": "2026-09-11 17:38:19",
"open_orders": 0,
"changes_detected": 230,
"note": "revenue = landed value credited (USDC/USDT at 6-dec, ETH at live CoinGecko rate)",
"at": "2026-09-11T20:05:11.184Z"
}Register a customer and mint a bearer key. The key is shown once; only its SHA-256 hash is stored.
customer.0 for a clean key; the storefront mints clean customers.{
"customer_id": 3,
"name": "my-agent",
"api_key": "4f2e…48 hex chars…",
"credits": 0,
"note": "store this key — bearer auth, hashed at rest, shown once"
}Start watching a URL. Optional webhook receives target.changed on every change. The target is polled immediately, then on the poll cadence.
Stop watching a target. You can only delete targets you created.
// 200
{ "deleted": true }
// 404 — not your target, or no such targetRecent change feed for your targets. ?limit=N (default 20). Each row records one detected change across all watched pages.
[
{ "id": 44, "target_id": 12, "url": "https://example.com",
"from_hash": "0f2a…", "to_hash": "9c1e…", "type": "content changed",
"at": "2026-09-11 12:34:56" }
]Poll a target right now (on demand). Debits 1 credit and records a fresh fingerprint. Returns the poll result — {hash, length, changed}.
Your balance and full ledger.
{
"customer": "my-agent",
"balance_credits": 834,
"plan": null,
"usage": [
{ "kind": "topup", "credits": 900, "at": "…" },
{ "kind": "check", "credits": -1, "at": "…" }
]
}Quote a live invoice. Watchbot then scans its wallet on Base + Ethereum and auto-credits the moment your payment confirms — credits = dollars that landed × 100.
credits: 900 → min_usdc: 9.000000.{
"order_id": 19,
"status": "pending",
"asset": "USDC", "min_usdc": "9.000000", "credits": 900,
"recipient": "0x0732C669c7C971DaD117a58e22F93D70c111233C",
"accept_on": [
{ "chain": "Base", "tokens": ["USDC","USDT"], "explorer": "https://basescan.org/tx/" },
{ "chain": "Ethereum", "tokens": ["USDC","USDT"], "explorer": "https://etherscan.io/tx/" }
],
"also_accept": [
{ "asset": "ETH", "chain": "Ethereum", "note": "native ETH, any amount — credited at the live CoinGecko rate" }
],
"expires_at": "2026-09-12T17:38:19.000Z",
"how": "send USDC or USDT (any amount ≥ the minimum) on any listed chain, or native ETH on Ethereum, to the recipient. Watchbot credits you automatically — credits = dollars you sent × 100 …"
}Your orders and their lifecycle: pending → paid as the watcher sees the chain confirm. Invoices auto-revoke after 60 minutes if unused — a lapsed invoice never charges anything.
Lead Radar — business leads from several sources, 10¢ per lead with email
Say who and where ("dentists in Austin TX"). Lead Radar searches Google Maps, OpenStreetMap and Wikidata (plus Foursquare, TomTom and Geoapify where enabled), merges them into one record per business, and returns the businesses with name, address, phone, website, rating, review count and — where the business publishes one — an email found on its own site. Leads with an email cost 10 credits (10¢); leads without one are free. The easiest way in is the Lead Radar panel in your dashboard.
Create a lead list and start scanning straight away (results in 2–5 minutes).
dentists in Austin TX.{"profile_id":…, "cost_per_email_lead":10, "capacity":…}. GET /leadgen-profiles lists your lists and their last run; POST /leadgen-profiles/:id/run scans again; DELETE removes a list.The leads in a list. Add /leadgen-results/:id.csv for a CSV download. Each lead carries sources, quality (0–100), email_check (mail server found), email_type (personal / role) and socials. Emails held back for lack of credits show as locked and unlock automatically after a top-up.
Radars — ranked, 5¢ a match
Two sibling radars run on the same rail and the same honest rule: nothing is delivered unpaid, and a ranked match costs 5 credits (5¢) from your prepaid balance. Grant Radar ranks non-dilutive grant feeds hourly. Tender Radar ranks World Bank procurement + US Federal Register notices deadline-first and records the exact terms each match hit (matched_terms), so a capability profile self-corrects instead of being a blind keyword box. Both are idempotent — every opportunity delivers to a profile exactly once, and a fresh profile catches up on the recent pool on its first scan.
Arm Grant Radar with a keyword profile. Each ranked match debits 5 credits (5¢).
my grant watch.women climate education.{"profile_id":…, "cost_per_match":5, "capacity":…, …} — capacity is your balance ÷ 5. GET lists your profiles, DELETE /grant-profiles/:id removes one you own.Your ranked grant matches — title, summary, source, publish date, score. Every row was debited 5 credits at delivery; nothing is listed unpaid.
Arm Tender Radar with a capability profile — e.g. water bangladesh construction consultancy. Same shape and pricing as grant-profiles; scans the World Bank procurement board and the US Federal Register hourly, and ranks soonest-closing bids first.
Your tender matches, sorted deadline-first — open-ended rows sort last. Each row carries why it matched.
{
"matches": [
{ "title": "…Rural Water Supply…", "source": "worldbank",
"fit": 3, "matched_terms": "water, construction, consultancy",
"deadline": "2026-10-02T00:00:00Z", // ISO; missing deadline sorts last
"sector": "…", "country": "Congo, Democratic Republic of",
"meta": "{\"method\":\"Request for Quotations\",\"ref\":\"…\"}",
"link": "https://projects.worldbank.org/…",
"delivered_at": "…" }
]
}Pulls the NVD CVE feed every 6 hours and alerts only when a CVE's description or CPE touching your technologies clears your severity threshold. Each ranked alert debits 5 credits.
curl -X POST https://store.watchtbot.site/vuln-profiles \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"web stack","technologies":"nodejs express postgresql","severity_threshold":"high"}'
{"profile_id":…, "cost_per_alert":5, "capacity":…} — capacity is your balance ÷ 5. GET /vuln-alerts lists ranked alerts (cve_id, severity, matched technologies). DELETE /vuln-profiles/:id removes one.Scans arXiv daily against your keywords. Each ranked paper debits 2 credits — titles, abstracts, authors via GET /paper-matches.
curl -X POST https://store.watchtbot.site/research-profiles \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"my research watch","keywords":"transformer calibration rlhf"}'
{"profile_id":…, "cost_per_match":2, "capacity":…} — DELETE /research-profiles/:id removes one.Watches npm/PyPI/RubyGems/crates.io releases plus OSV advisories for the packages you name, every 6 hours. Updates debit 3 credits; one debit per newly vulnerable package.
curl -X POST https://store.watchtbot.site/dep-profiles \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"my deps","package_registry":"npm","dependencies":["lodash","express"]}'
{"profile_id":…, "cost_per_update":3} — GET /dep-updates lists version bumps + advisories. DELETE /dep-profiles/:id removes one.Watch a CoinGecko coin for price thresholds, checked every 15 minutes. 2 credits per check, 5 per fired alert (one fire per condition per hour).
curl -X POST https://store.watchtbot.site/crypto-alerts \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"coin_id":"bitcoin","above":100000,"below":60000}'
{"watch_id":…, "check_cost_credits":2, "alert_cost_credits":5} — GET /crypto-alerts lists watches, GET /crypto-history?watch_id=… sparkline data, DELETE /crypto-alerts/:id removes one.Watch any of 166 currency pairs off the er-api feed, checked hourly. 2 credits per check, 5 per fired alert (max one per 45 min).
curl -X POST https://store.watchtbot.site/fx-alerts \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"base":"USD","quote":"INR","below":92}'
{"alert_id":…} — GET /fx-alerts lists alerts, GET /fx-history?alert_id=… history, DELETE /fx-alerts/:id removes one.Watch NWS active alerts for US states, scanned every 15 min. 3 credits per sweep that surfaces a new matched event, 5 per alert sent.
curl -X POST https://store.watchtbot.site/weather-profiles \
-H "Authorization: Bearer YOUR_KEY" -H "Content-Type: application/json" \
-d '{"states":"NC,VA","severity_threshold":"severe"}'
{"profile_id":…, "sweep_cost_credits":3} — GET /weather-profiles lists profiles, GET /weather-matches matched events, DELETE /weather-profiles/:id removes one.04Webhook delivery
When a watched page's fingerprint changes, Watchbot POSTs to your webhook_url with a JSON body. Payload:
// Header: content-type: application/json · user-agent: Watchbot/0.2
{
"event": "target.changed",
"target_id": 12,
"url": "https://example.com",
"from_hash": "0f2a1b…", // previous fingerprint
"to_hash": "9c1e77…", // new fingerprint — different ⇒ content changed
"at": "2026-09-11 12:34:56"
}| Property | Semantics |
|---|---|
from_hash → to_hash | Two hashes differ ⟺ something changed. Compare, don't diff the page yourself. |
event | Always target.changed for now — a versioned event name for the future. |
at | Detection time, UTC. |
05Rails · credits · invoices
The automaton earns on a self-custody wallet — no platform escrow between payer and earner.
| Asset | Chain | Credited at |
|---|---|---|
USDC | Base · Ethereum | 6 decimals — 0.01 USDC = 1 credit |
USDT | Base · Ethereum | 6 decimals |
ETH (native) | Ethereum | live CoinGecko USD rate → floor(USD × 100) |
06Errors
| Status | Meaning |
|---|---|
| 400 | Bad request — e.g. missing url, non-HTTP scheme, credits < 1, unknown lead status. |
| 401 | Missing / invalid bearer token. |
| 404 | No such route, target, or resource. |
| 500 | Something broke. The error field says what. |
| 503 | Billing not configured (shouldn't happen on a live rail). |
Errors are always JSON: {"error":"…"}.
06bfetch402 — pay-per-call web intel (x402-style, no account)
The MCP tools above need an API key; fetch402 does not. It is a standalone x402-style rail where your agent pays per call in USDC on Base and the server verifies the transfer on-chain before serving. Full reference at /x402; machine-readable spec:
https://store.watchtbot.site/x402/v1/spec # tools + prices https://store.watchtbot.site/x402/llms.txt # agent-readable docs
| Tool | Endpoint | Price |
|---|---|---|
fetch | GET /x402/v1/fetch?url=… → clean text of any URL | 1¢ USDC |
price | GET /x402/v1/price?url=… → structured {marketplace, price, currency, title} | 2¢ USDC |
Flow: unpaid call answers 402 with payment details → send exact USDC on Base → repeat with header X-PAYMENT-TX: <tx hash> → content served. One tx = one delivered call; failed calls never consume your tx.
07MCP Server — tools for AI agents
Every product runs as an Model Context Protocol server, so Claude, ChatGPT, or your own automaton can call the tools directly and pay per call from the same credit balance. Endpoint:
https://mcp.watchtbot.site/mcp
Connect from Claude Code
claude mcp add --transport http web4 https://mcp.watchtbot.site/mcp
Tools & pricing (1 credit = 1¢)
| Tool | What it does | Cost |
|---|---|---|
automaton_get_balance | credit balance + usage history | free |
watchbot_watch_url | register a URL change watch | 1¢ / check |
watchbot_list_changes | recent detected diffs | free |
leadradar_create_profile | build a multi-source lead list (maps, open data, the business's own site) | 10¢ / lead with email |
leadradar_list_leads | fetch collected leads | free |
grantradar_set_profile | arm grant keyword radar | 5¢ / match |
grantradar_get_matches | ranked grant matches | free |
tenderradar_get_matches | tender matches, deadline-first | free (plan-gated) |
whalebot_arm_watch | on-chain event watch (Base/ETH) | 10¢ arm · 5¢ / alert |
fetch402_fetch | fetch any URL as clean text (no key — x402 rail, agent pays per call) | 1¢ / fetch |
fetch402_price | structured price from Amazon/Flipkart/any product page (no key — x402 rail) | 2¢ / lookup |
Auth
Every tool takes an api_key argument — the same key as the REST API. Register one free at the storefront, top up with USDC/USDT/ETH, and the tools debit that balance per call. Insufficient balance returns a 402 with a top-up hint.
Example session
# 1. check balance (free)
automaton_get_balance { "api_key": "…" }
# 2. queue a lead scrape — first run usually done in minutes
leadradar_create_profile { "api_key": "…", "query": "dentists in Austin TX", "depth": 1 }
# 3. collect results
leadradar_list_leads { "api_key": "…", "profile_id": 42, "emails_only": true }