Watchbot · http api · v0.2

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.

live below is real — — credits issued — settlements — changes → webhooks —

Endpoints

GET/healthliveness + occupancy
GET/tickerpublic earnings (settled only)
POST/keysregister → bearer key (shown once)
POST/watchadd a URL to watch (auth)
DELETE/watch/:idstop watching, own targets
GET/changesrecent change feed (auth)
GET/changes/:idpoll a target on demand (1 credit)
GET/usagebalance + ledger (auth)
POST/purchasequote a live invoice (auth)
GET/ordersyour orders + status (auth)
POST/grant-profilesarm the funding radar (auth)
GET/grant-matchesranked grant matches (auth)
POST/tender-profilesarm the tender radar (auth)
POST/vuln-profilesarm the CVE radar (auth)
POST/research-profilesarm the paper radar (auth)
POST/dep-profilesarm the stack monitor (auth)
POST/crypto-alertsarm a coin price alert (auth)
POST/fx-alertsarm a currency-pair alert (auth)
POST/weather-profilesarm a NWS state watch (auth)
GET/tender-matchesdeadline-first tender matches (auth)
start here

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" automatically
the rail

02Authentication

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>.

PRIVATEKeep the key server-side. Anyone holding it can spend your credits and add targets.
ScenarioStatusBody
Missing or bad token401{"error":"missing/invalid bearer token — register via POST /keys"}
Public/open endpoints first—/health, /ticker, POST /keys need no token
reference

03Endpoints

GET/healthopen

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
}
GET/tickeropen

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"
}
POST/keysopen

Register a customer and mint a bearer key. The key is shown once; only its SHA-256 hash is stored.

namestringOptional label, ≤ 60 chars. Default customer.
start_creditsint ≥ 0Initial balance. 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"
}
POST/watchauth

Start watching a URL. Optional webhook receives target.changed on every change. The target is polled immediately, then on the poll cadence.

urlstringHTTP/HTTPS only. Must already be registering a well-formed target.
webhook_urlstringCalled on change. Optional.
201Each check costs 1 credit from your balance. Empty balance → checks queue but don't fire; top up to resume.
DELETE/watch/:idauth

Stop watching a target. You can only delete targets you created.

// 200
{ "deleted": true }
// 404 — not your target, or no such target
GET/changesauth

Recent 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" }
]
GET/changes/:idauth

Poll a target right now (on demand). Debits 1 credit and records a fresh fingerprint. Returns the poll result — {hash, length, changed}.

GET/usageauth

Your balance and full ledger.

{
  "customer": "my-agent",
  "balance_credits": 834,
  "plan": null,
  "usage": [
    { "kind": "topup",    "credits": 900,  "at": "…" },
    { "kind": "check",    "credits": -1,   "at": "…" }
  ]
}
POST/purchaseauth

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.

creditsint ≥ 11 credit = 1¢, so 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 …"
}
CREDITS MATHUSDC/USDT credited at 6 decimals; native ETH at the live CoinGecko USD rate, flooring to whole credits. If an exchange withdrawal fee shaves a few cents below the minimum, you still get credited for the landed amount.
GET/ordersauth

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 · product #08

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.

POST/leadgen-profilesauth

Create a lead list and start scanning straight away (results in 2–5 minutes).

querystringWho and where — e.g. dentists in Austin TX.
namestringLabel, ≤ 60 chars.
depth1–3How far to scroll the map. Default 1 (≈20 businesses).
max_resultsintCap, ≤ 200. Default 50.
201{"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.
GET/leadgen-results?profile_id=…auth

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.

funding & tender radars · products #02 · #03

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.

POST/grant-profilesauth

Arm Grant Radar with a keyword profile. Each ranked match debits 5 credits (5¢).

namestringLabel, ≤ 60 chars. Default my grant watch.
keywordsstringComma/space separated, ≤ 500 chars — e.g. women climate education.
201{"profile_id":…, "cost_per_match":5, "capacity":…, …} — capacity is your balance ÷ 5. GET lists your profiles, DELETE /grant-profiles/:id removes one you own.
GET/grant-matchesauth

Your ranked grant matches — title, summary, source, publish date, score. Every row was debited 5 credits at delivery; nothing is listed unpaid.

POST/tender-profilesauth

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.

GET/tender-matchesauth

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": "…" }
  ]
}
POST/vuln-profilesauth

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"}'
201{"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.
POST/research-profilesauth

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"}'
201{"profile_id":…, "cost_per_match":2, "capacity":…} — DELETE /research-profiles/:id removes one.
POST/dep-profilesauth

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"]}'
201{"profile_id":…, "cost_per_update":3} — GET /dep-updates lists version bumps + advisories. DELETE /dep-profiles/:id removes one.
POST/crypto-alertsauth

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}'
201{"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.
POST/fx-alertsauth

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}'
201{"alert_id":…} — GET /fx-alerts lists alerts, GET /fx-history?alert_id=… history, DELETE /fx-alerts/:id removes one.
POST/weather-profilesauth

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"}'
201{"profile_id":…, "sweep_cost_credits":3} — GET /weather-profiles lists profiles, GET /weather-matches matched events, DELETE /weather-profiles/:id removes one.
push

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"
}
PropertySemantics
from_hash → to_hashTwo hashes differ ⟺ something changed. Compare, don't diff the page yourself.
eventAlways target.changed for now — a versioned event name for the future.
atDetection time, UTC.
BEST-EFFORTDelivery is a single attempt with a 5s timeout — no retry queue yet. If your endpoint is down, that one change event is lost; the next detected change fires fresh. Respond 2xx to acknowledge.
billing

05Rails · credits · invoices

The automaton earns on a self-custody wallet — no platform escrow between payer and earner.

AssetChainCredited at
USDCBase · Ethereum6 decimals — 0.01 USDC = 1 credit
USDTBase · Ethereum6 decimals
ETH (native)Ethereumlive CoinGecko USD rate → floor(USD × 100)
ANY AMOUNTSend any amount ≥ the quoted minimum on any accepted chain — not just the exact figure. Landing $0.07 under the minimum still credits the landed value.
ops

06Errors

StatusMeaning
400Bad request — e.g. missing url, non-HTTP scheme, credits < 1, unknown lead status.
401Missing / invalid bearer token.
404No such route, target, or resource.
500Something broke. The error field says what.
503Billing not configured (shouldn't happen on a live rail).

Errors are always JSON: {"error":"…"}.

agents

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
ToolEndpointPrice
fetchGET /x402/v1/fetch?url=… → clean text of any URL1¢ USDC
priceGET /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.

agents

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¢)

ToolWhat it doesCost
automaton_get_balancecredit balance + usage historyfree
watchbot_watch_urlregister a URL change watch1¢ / check
watchbot_list_changesrecent detected diffsfree
leadradar_create_profilebuild a multi-source lead list (maps, open data, the business's own site)10¢ / lead with email
leadradar_list_leadsfetch collected leadsfree
grantradar_set_profilearm grant keyword radar5¢ / match
grantradar_get_matchesranked grant matchesfree
tenderradar_get_matchestender matches, deadline-firstfree (plan-gated)
whalebot_arm_watchon-chain event watch (Base/ETH)10¢ arm · 5¢ / alert
fetch402_fetchfetch any URL as clean text (no key — x402 rail, agent pays per call)1¢ / fetch
fetch402_pricestructured 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 }