where predicate plus one processing checkpoint
that smry keeps for you. Your agent polls, processes, acknowledges, and exits.
It stores no cursor, no database row, no file — and it still never re-reads or
misses an entry.
This is the difference between a scheduled agent that needs infrastructure and
one that needs an API key.
Which checkpoint owner to use
Both modes read the same ordered stream throughPOST /v1/feeds/changes. They
differ in who remembers the position.
Caller-managed cursors are covered in Follow sources. The rest
of this page is the managed path.
Create one consumer per independent processing loop — not per source. A single
collection predicate can cover thousands of sources.
Create a consumer
POST /v1/feeds/consumers takes a name and an optional where. The
predicate uses the same vocabulary as POST /v1/feeds/query; omit it to
consume everything the account follows.
Response
where can differ from what you sent.
Creation is idempotent by name. Retrying with the same case-insensitive
name and the same predicate returns the existing consumer, with its
position intact — a retry after a timeout never resets progress. The same
name with a different predicate is 409 consumer_name_taken. Delete it,
reuse the original predicate, or pick another name.A new consumer starts at now
If you also need the entries that already exist, do it in this order:1
Create the consumer first
From this moment forward, everything new is captured.
2
Then query the historical window
POST /v1/feeds/query with the same predicate and a since or
where.discovered_after bound.3
Then start polling
The consumer hands you everything discovered after step 1.
Poll, process, acknowledge
Passconsumer_id to POST /v1/feeds/changes. Pass exactly one checkpoint:
sending cursor and consumer_id together is a 400, and so is sending
where with consumer_id — the consumer already stores its predicate.
Response
next_cursor. The path carries the consumer
id, so the body needs only the cursor.
Response
The rules that make this safe
- Polling does not advance the checkpoint. Only an ack does. A batch is
redelivered on every poll until its
next_cursoris acknowledged. Crash halfway through and the next run gets the same events again. - Make your effects idempotent. Redelivery is the failure mode you are buying; at-least-once delivery is the guarantee, not exactly-once.
- Acknowledge an empty batch too.
ack_requiredstaystrue. Skipping the ack leaves the checkpoint where it was and the same window is re-scanned forever. - Acknowledge only the cursor the poll just handed you. A stale cursor, a cursor from another consumer, and an invented cursor are all rejected. Acknowledgements only move forward.
- Acknowledging the same cursor twice is safe. It is the current position,
so the call succeeds and only
last_acknowledged_atmoves. - Keep polling while
has_moreistrue, acknowledging each batch before fetching the next.limitdefaults to 100 and caps at 500. - Consumer progress is not human read state. Acking never marks anything read in the reader UI.
A complete polling loop
Both of these are the whole scheduled job: no local storage, safe to run on a cron, safe to kill at any point.Inspect and remove consumers
GET /v1/feeds/consumers lists every consumer with its saved predicate, its
timestamps, and pending — how many matching entries are waiting past its
acknowledged checkpoint. A pending count that keeps climbing means a loop is
polling without acking, or is not running.
Response
DELETE /v1/feeds/consumers/{id} removes only the saved predicate and the
checkpoint. Followed sources, entries, and human read state are untouched.
Response
Next
Follow sources
Discover, follow, query, and mutate the stream a consumer reads from.
Watch a page for changes
Feed a consumer from a page that publishes no feed at all.
Content safety
Every entry a consumer delivers was written by someone else.