webhook_url is optional. Omit it and the watch still polls and records every match — read
them with GET /v1/{source}/watches/{id}/events, which is free. See
Reading matches without a webhook.
Lifecycle
Day-one backfill
A new watch starts empty: it emits nothing until its first tick finds something the second tick has not already seen. Passbackfill: true on create and it runs one
immediate search and stores the results as its starting history, so
GET /v1/{source}/watches/{id}/events answers with content straight away.
POST answers 201 with the watch as soon
as the backfill is queued, rather than blocking on an upstream search — so the events
appear a moment later, not in the response body.
Events that came from a backfill carry "seeded": true. Everything the watch catches on
its own ticks from then on is a normal event, delivered normally: the first scheduled tick
compares against what the backfill saw, so nothing is emitted twice.
The same knob exists at Query scale: backfill: true on POST /v1/queries runs one such
search per enabled Source, charged one tick each at that Source’s rate. See
History & backfill for what a Query starts with.
Match mode
Search platforms do not all match the way you would expect. Algolia searches Hacker News authors with typo-and-prefix tolerance, Reddit’s search is fuzzy, and YouTube ranks by relevance rather than text — so a watch on a distinctive brand name can return items that do not carry it anywhere.match is an optional body field on watch creation for the six Sources that take a
query — X, Reddit, Youtube, Bluesky, Hacker News and RSS. It says how strictly a result
must carry your query:
Under
word and contains, an item whose text misses is still kept when one of its
identities — the author’s handle, the subreddit, the channel — is exactly your query.
A post by @nephia is not noise, whatever it happens to say.match does not touch. A leg with no query — a Youtube channel, a Bluesky
author, an RSS feed_url, a Reddit subreddit on its own — has no term to be held to,
so nothing is filtered. And Vinted watches do not take the field at all: catalog search is
not a text match, and a listing titled “501” is a correct hit for “jean levis 501”.
One leg runs the other way. A Reddit Watch in
types: "comments" reads a listing, not a search, so a query
on it is a filter we apply rather than a search the platform ran — and it applies under
every match mode, platform included. Setting word or contains there tightens our
filter; leaving match unset does not turn it off.
A Watch in types: "all" runs both ways at once: its
posts half is searched by Reddit, its comments half is filtered by us, under one match.
That asymmetry is the platform’s, not a setting — match: "word" is what tightens both.
The pull endpoints (GET /v1/{source}/search and friends) are unaffected — they return
what the platform returned, as they always have.
Per-Source limits
Billing
Each poll tick charges 4 credits for X, Reddit, and Vinted; 5 credits for Youtube; 1 credit for Bluesky, Hacker News and RSS — before the poll runs. AI answers charges 100 credits per engine per run, after each engine answers, and an engine that errors is not charged. See Credits — Watch billing. When credits are insufficient, the watch is auto-paused withpausedReason: insufficient_credits. Polling does not resume automatically — call POST /v1/{source}/watches/{id}/resume after topping up.
A watch created with backfill: true charges one extra tick at the same
rate, for the one immediate search it runs.
A watch created with an agent step (ai on the create body) charges that
step on top of the tick — 1 credit per 25 events read — and its delivery waits
for the reading, up to 120 seconds. The event is delivered either way: see
Webhooks — Agent step on a watch.
Pause reasons
pausedReason tells you who stopped a watch and what unblocks it.
resume re-checks your plan cap and interval, so it fails with 400 VALIDATION while
the watch still exceeds them.
Activity log
GET /v1/{source}/watches/{id}/activity returns operational events for a watch: poll.completed, poll.failed, webhook.delivered, webhook.failed. Each entry includes creditsCharged, eventsEmitted, and delivery metadata where applicable.
Reading matches without a webhook
webhook_url is optional on create. A watch without one polls on the same interval, costs
the same per tick, and records every match — it just does not push. Read what it caught: