Guides
Recipes
Each body goes to POST /v1/watchers. Add your own notify block; it's left out here.
Size back in stock
One size of a product on a Shopify store.
{
"target": { "query": "https://www.allbirds.com/products/mens-wool-runners", "match": "url", "attributes": { "size": 10 } },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "back_in_stock" }] },
"where": { "sites": ["allbirds.com"] },
"schedule": { "every": "1h", "until": "+30d" }
}back_in_stock is true only on the run where it comes back. Use in_stock to hear as soon as it's available, even on the first run.
Price drop from today
10% under the price on the first check.
{
"target": { "query": "https://www.sweetwater.com/store/detail/SM58--shure-sm58-cardioid-dynamic-vocal-microphone", "match": "url" },
"conditions": { "all": [{ "field": "price_drop", "op": ">=", "value": "10%", "compare_to": "first_snapshot" }] },
"where": { "sites": ["sweetwater.com"] },
"schedule": { "every": "6h", "until": "+90d" }
}Cheapest of several stores
The lowest price for one size, across stores.
{
"target": { "query": "Brooks Ghost 17", "match": "exact", "attributes": { "size": 10 } },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "in_stock" }, { "field": "price", "op": "<=", "value": 120 }] },
"where": { "sites": ["brooksrunning.com", "runningwarehouse.com", "zappos.com"], "aggregate": "best" },
"schedule": { "every": "1h", "until": "+30d" }
}New product at a store
Anything new with "Q1" in its name.
{
"target": { "query": "Q1", "match": "line", "exclude": ["Q1 Pro"] },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "in_stock" }] },
"where": { "sites": ["keychron.com"] },
"schedule": { "every": "1h", "until": "+60d" },
"notify": { "channels": ["webhook"], "repeat": "on_change", "webhook": { "url": "https://you.example/hook", "events": ["matched", "changed"] } }
}changed fires when a new product appears in the results.
New post on a subreddit
A Pilot Custom 823 for sale on r/Pen_Swap, at $250 or less.
{
"target": { "query": "Custom 823", "match": "line", "exclude": ["WTB"] },
"conditions": { "all": [
{ "field": "availability", "op": "=", "value": "in_stock" },
{ "field": "price", "op": "<=", "value": 250 }
] },
"where": { "sites": ["reddit.com/r/Pen_Swap"] },
"schedule": { "every": "5m", "until": "+30d" },
"notify": { "channels": ["webhook"], "repeat": "on_change", "webhook": { "url": "https://you.example/hook", "events": ["matched", "changed"] } }
}Each result's url is the post. seller is the author, date is when it was posted, and price is the first dollar amount in the title or text. A post flaired sold, closed or completed is out_of_stock. The first webhook lists the posts that already match; after that, changed fires when a new one appears.
Next release of a repo
The first Node.js release at 25 or above.
{
"target": { "query": "node", "match": "line" },
"conditions": { "all": [{ "field": "version", "op": ">=", "value": 25 }, { "field": "release_status", "op": "=", "value": "available" }] },
"where": { "sites": ["github.com/nodejs/node"] },
"schedule": { "every": "1h", "until": "after_first_match" }
}Prereleases come through as announced, so available skips them.
Two sizes at the same store
Size 10 and 10.5, both in stock from one seller.
{
"target": { "query": "Brooks Ghost 17", "match": "exact", "attributes": { "size": [10, 10.5] } },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "in_stock", "over": { "key": "sizes", "require": "all", "same": "seller" } }] },
"where": { "sites": ["runningwarehouse.com", "zappos.com"] },
"schedule": { "every": "1h", "until": "+14d" }
}New product, then its price
When a store lists a new "Q6 HE", start watching that product for a price under $200. See Follow-up watchers.
{
"target": { "query": "Q6 HE", "match": "line" },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "in_stock" }] },
"where": { "sites": ["keychron.com"], "aggregate": "first" },
"schedule": { "every": "1h", "until": "+180d" },
"then": {
"conditions": { "all": [{ "field": "price", "op": "<", "value": 200 }] },
"schedule": { "every": "6h", "until": "+90d" }
}
}A booked time opens up
A 15-minute slot on a Cal.com page, any morning next week, in New York time.
{
"target": { "query": "cal.com/peer/15", "match": "listing", "attributes": { "from": "2026-11-09", "to": "2026-11-13", "time_zone": "America/New_York" } },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "open_slot" }] },
"where": { "sites": ["cal.com/peer/15"] },
"schedule": { "every": "5m", "until": "+7d" }
}Cal.com lists only open times, so a booked time drops out of the results. When someone cancels, it comes back and you get changed, even inside the cooldown. Each result's date is the start with its UTC offset.
A job at a company
A new remote engineering job on Discord's Greenhouse board, leaving out internships.
{
"target": { "query": "engineer", "match": "line", "attributes": { "remote": true }, "exclude": ["intern"] },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "in_stock" }] },
"where": { "sites": ["boards.greenhouse.io/discord"] },
"schedule": { "every": "15m", "until": "+30d" },
"notify": { "channels": ["webhook"], "repeat": "on_change", "webhook": { "url": "https://you.example/hook", "events": ["matched", "changed"] } }
}An open job is in_stock. The first webhook lists the jobs that already match; after that, changed fires when one is posted. Lever, Ashby and Workday boards work the same way.
A token moves 0.5% in 10 minutes
BONK on Solana, either way, read from DexScreener every minute.
{
"target": { "query": "https://dexscreener.com/solana/DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", "match": "url" },
"conditions": { "all": [{ "field": "price_move", "op": ">=", "value": "0.5%", "compare_to": "window", "window": "10m" }] },
"where": { "sites": ["dexscreener.com"] },
"schedule": { "every": "1m", "until": "+7d" },
"notify": { "channels": ["webhook"], "repeat": "every_match", "cooldown": "5m", "webhook": { "url": "https://you.example/hook", "events": ["matched"] } }
}The link can be a pair or a token address. Prices are exact, so a move on a $0.000028 token is measured on the real number. The window compares with the oldest check in the last 10 minutes; see compare_to.
A status page incident
Cloudflare's Durable Objects component leaves operational, or any incident names it.
{
"target": { "query": "Durable Objects", "match": "line" },
"conditions": { "any": [
{ "field": "status", "op": "changed" },
{ "field": "status", "op": "in", "value": ["partial_outage", "major_outage"] }
] },
"where": { "sites": ["statuspage:www.cloudflarestatus.com"] },
"schedule": { "every": "5m", "until": "+30d" }
}Each component, incident and maintenance is a record with a status and a record_type (component, incident, maintenance or page). To watch the whole page, use match: listing. A status page on its own domain needs the statuspage: or instatus: prefix.
A page's text changes
Python's downloads page lists a new release.
{
"target": { "query": "https://www.python.org/downloads/", "match": "url", "attributes": { "selector": ".download-list-widget .release-number", "contains": "Python 3.16.0" } },
"conditions": { "any": [
{ "field": "contains", "op": "=", "value": true },
{ "field": "text_hash", "op": "changed" }
] },
"where": { "sites": ["change:https://www.python.org/downloads/"] },
"schedule": { "every": "1h", "until": "+30d" }
}This is the loosest kind of watch: it tells you the text changed or the phrase appeared, not what it means. Preview it first and check that the text is the part you meant. See Page changes for every attribute.
A haircut at a salon, under a price
Robert's regular haircut at a Booksy barber shop, once it's $40 or less.
{
"target": { "query": "https://booksy.com/en-us/388730_barber-shop-nyc_barber-shop_30067_new-york-city", "match": "url", "attributes": { "service": "Regular Haircut", "staff": "Robert" } },
"conditions": { "all": [{ "field": "price", "op": "<=", "value": 40 }] },
"where": { "sites": ["booksy.com/en-us/388730_barber-shop-nyc_barber-shop_30067_new-york-city"] },
"schedule": { "every": "6h", "until": "+30d" }
}Booksy and Fresha give the salon's service menu: one result per service, with its price, length and staff. They don't give open times. Booksy's time-slot API is disallowed by its robots.txt, and Fresha opens a cart and can show a captcha for each search. Vagaro and Boulevard wait on a review. To be told when a time opens, the salon's own booking page has to be on a reader that lists times, such as Cal.com (see A booked time opens up) or Jane App for clinics.
A bill's next action
Senate bill 3331 moves: a committee reports it, it passes, or it's signed.
{
"target": { "query": "https://www.congress.gov/bill/119th-congress/senate-bill/3331", "match": "url" },
"conditions": { "all": [{ "field": "latest_action", "op": "changed" }] },
"where": { "sites": ["congress.gov"] },
"schedule": { "every": "1h", "until": "+180d" }
}Each run reads the bill's latest action and its date. status is read from that wording (introduced, reported, passed, to_executive, became_law, vetoed, failed, in_progress), so { "field": "status", "op": "=", "value": "became_law" } waits for the end. It's our reading of the text, not an official field. Needs CONGRESS_API_KEY on the server. State bills work the same way through Open States (openstates.org/nc/bills/2025/HB1105).
A PDF changes, and what changed
The IRS posts a new revision of Form W-9.
{
"target": { "query": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "match": "url" },
"conditions": { "all": [{ "field": "text_hash", "op": "changed" }] },
"where": { "sites": ["change:https://www.irs.gov/pub/irs-pdf/fw9.pdf"] },
"schedule": { "every": "1d", "until": "+180d" }
}A link ending in .pdf is read as a PDF: the result is a document with the text of every page, pages, pdf_title and pdf_modified. When the text changes, last_change says what moved, like changed "Rev. October 2018" to "Rev. March 2024", with counts of paragraphs added, removed and changed. Add "summarize": true to the attributes for a one- or two-sentence summary from the server's LLM. A scanned PDF with no text layer reads as empty. The last version is kept in the database, so a deploy doesn't lose the "before" text.
Something new on a list
A new press release on CISA's newsroom.
{
"target": { "query": "https://www.cisa.gov/news-events/news", "match": "url", "attributes": { "list": "article.c-teaser" } },
"conditions": { "all": [{ "field": "items", "op": "added" }] },
"where": { "sites": ["change:https://www.cisa.gov/news-events/news"] },
"schedule": { "every": "1h", "until": "+30d" }
}list is a CSS selector for the repeated item. Each match becomes a record keyed by its link, with its title, href, position and the date from its <time>. The first run is the starting list, so nothing is added on it. { "field": "items", "op": "removed" } tells you when one leaves. items works with any reader that returns its whole list, such as exchange listings or a dataset; see Lists.
A change an AI checks for you
Notion's pricing page changes, and an LLM reads it to see whether a price went up.
{
"target": { "query": "https://www.notion.com/pricing", "match": "url" },
"conditions": { "all": [
{ "field": "text", "op": "judge", "value": "a plan's price went up, or a plan was removed" }
] },
"where": { "sites": ["change:https://www.notion.com/pricing"] },
"schedule": { "every": "6h", "until": "+90d" }
}The judge is asked only when the text changed. It reads change_excerpt, the lines that changed with a line either side, so a change far down a long page is still judged. The event carries the judge's reason in judged. Each watcher gets 20 judged checks a day; a change the budget held back is judged on a later run, and fires then. That's why there's no text_hash changed next to it: on that later run the text hasn't changed again, so the pair could never both be true. Previews don't call the judge. See Judged conditions.
A city council agenda item
Seattle puts rezoning on a meeting agenda.
{
"target": { "query": "rezoning", "match": "line", "attributes": { "days": 30 } },
"conditions": { "all": [{ "field": "items", "op": "added" }] },
"where": { "sites": ["legistar:seattle"] },
"schedule": { "every": "6h", "until": "+90d" }
}Words read the agenda items of the next 12 meetings in the coming days. Each matching item is a record with its matter file, type, status, the meeting's start time and the agenda PDF. The first run is the starting list; after that, items added fires when an agenda with the words is posted. Any city or county on Legistar works the same way (legistar:<client>, or <client>.legistar.com). For every upcoming meeting instead, use match: listing.
A domain coming free
Hear when a name you want is no longer registered, or starts on its way back to the pool.
{
"target": { "query": "shoe.dev", "match": "listing" },
"conditions": { "any": [
{ "field": "available", "op": "=", "value": true },
{ "field": "pending_delete", "op": "=", "value": true }
] },
"where": { "sites": ["rdap:shoe.dev"] },
"schedule": { "every": "6h", "until": "+180d" }
}This reads the registry's RDAP record, which replaced WHOIS. available is true when the registry has no record. Reserved and premium names can look the same, and a name can be taken between our check and your purchase, so treat it as a reason to go and look. For your own domain, { "field": "days_to_expiry", "op": "<", "value": 30 } warns you before it lapses. Some endings can't be watched: .de and .io have no RDAP server, and .uk and .xyz keep us out with robots.txt.
A Square appointment opening
A Tier 1 appointment at a Square-booked salon, any time in the next two weeks.
{
"target": { "query": "https://book.squareup.com/appointments/006k40agnfwses/location/L5TQE05VR6K90/services", "match": "listing", "attributes": { "service": "Medium (Tier 1)", "from": "2026-11-09", "to": "2026-11-22" } },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "open_slot" }] },
"where": { "sites": ["book.squareup.com/appointments/006k40agnfwses/location/L5TQE05VR6K90/services"] },
"fetch": { "accept_site_terms": true },
"schedule": { "every": "15m", "until": "+14d" }
}This one needs a terms review before use: Square's robots.txt disallows the API that lists open times, so a server refuses it with site_not_reviewed until someone at Snag has read Square's terms. Once reviewed, each open time is a slot with its start and offset, the exact price and the staff member; staff picks one person. Without the review, Square still gives the service menu with prices (match: line).
A Twitch channel going live
NASA's channel starts a stream.
{
"target": { "query": "https://www.twitch.tv/nasa", "match": "url" },
"conditions": { "all": [{ "field": "availability", "op": "=", "value": "back_in_stock" }] },
"where": { "sites": ["twitch.tv/nasa"] },
"fetch": { "accept_site_terms": true },
"schedule": { "every": "2m", "until": "+30d" }
}A channel is in_stock while it's live, so back_in_stock fires on the run it goes live. The result has the stream title, game, viewers and start time; { "field": "stream_title", "op": "changed" } catches a new title. This needs TWITCH_CLIENT_ID and TWITCH_CLIENT_SECRET on the server and a terms review before use (Twitch's API robots.txt disallows everything), so for now it's refused with site_unavailable or site_not_reviewed.
A wallet's token transfer
USDC arriving at an Ethereum address.
{
"target": { "query": "https://etherscan.io/address/0xd8da6bf26964af9d7eed9e03e53415d37aa96045", "match": "listing", "attributes": { "include": ["tokens"], "token": "USDC", "direction": "in" } },
"conditions": { "all": [{ "field": "items", "op": "added" }] },
"where": { "sites": ["etherscan.io/address/0xd8da6bf26964af9d7eed9e03e53415d37aa96045"] },
"fetch": { "accept_site_terms": true },
"schedule": { "every": "5m", "until": "+30d" }
}Each transfer is a record with value in USDC as a number, the sender, the transaction hash and its time. The first run is the starting list, so only new transfers fire. Polygonscan and Arbiscan addresses work the same way. You choose the address, and a wallet can belong to a private person, so that's your responsibility. This needs ETHERSCAN_API_KEY and a terms review before use (Etherscan's free plan is for personal and research use), so for now it's refused.