- Listing stream (Pro+): a short-lived SSE stream that delivers cached data immediately, then one fresh live fetch
- Persistent watchers (Business+): Durable Object-backed subscriptions that poll on a schedule and emit change events
Listing Stream (SSE)
GET /v1/listing/{id} opens a Server-Sent Events stream for a single listing. The stream emits:
error event is emitted with an error code and message, and the stream closes.
This is a short-lived stream: it completes once cached + live data have been delivered, usually within about 10 seconds. Size your client timeout off the worst case rather than the typical one, because a struggling upstream can take considerably longer: a single live fetch is allowed 30 seconds, and an upstream rate-limit or unavailability buys one retry, so a stream can stay open for a little over a minute before it closes.
Tier requirement: Pro, Business, or Internal.
Persistent Watchers
Watchers are long-lived subscriptions backed by Cloudflare Durable Objects. They support three watch types:Creating a watcher
Poll intervals
Each poll costs one API request against your active quota window.Streaming watcher changes
GET /v1/watchers/{id}/stream opens an SSE connection that emits change events:
These five are the complete set. No other event type is emitted on this stream.
heartbeat is not tied to your watcher’s pollInterval. The stream drains the
watcher’s change buffer on its own short, fixed cadence (currently about every 20
seconds) and emits a heartbeat whenever that drain turns up nothing new. Polling
upstream happens separately, on the watcher’s own schedule. A heartbeat therefore
means “the connection is alive and there is nothing new to hand you” — it does not
mean a poll just completed.id on each event is a resume cursor — see Resuming without missing
changes.
Managing watchers
- List:
GET /v1/watchers - Detail:
GET /v1/watchers/{id} - Pause/resume:
PATCH /v1/watchers/{id}with{"active": false}or{"active": true} - Change interval:
PATCH /v1/watchers/{id}with{"pollInterval": 600} - Delete:
DELETE /v1/watchers/{id}
Stream duration limit
Watcher SSE streams have a 5-minute maximum duration. When the limit is reached, the server sends areconnect event and closes the stream:
Resuming without missing changes
The watcher keeps a bounded buffer of the changes it has detected — the last 50 — and everychange, heartbeat, and reconnect event carries an SSE id that is a
cursor into that buffer. Replay the cursor on your next connection and the server
resumes from there instead of from the current tip, so changes detected while nothing
was connected are delivered rather than skipped.
There are two ways to send the cursor:
If both are present,
Last-Event-ID wins — on an automatic reconnect it is the fresher
of the two. A cursor must be a non-negative integer; anything else is treated as absent,
which is the same as a fresh connect.
- A connection with no cursor starts at the buffer tip. You get changes detected from that point on, not the watcher’s history. That is deliberate — otherwise every reconnect would replay the retained buffer as fresh alerts.
- Retention is bounded. If more than 50 changes accumulated while you were away, only what survives in the buffer can be replayed. A client that stays away that long gets what is left, not everything it missed.
changeids are per-change, so a client that drops mid-batch resumes at the change it actually received rather than at the start of the batch.- The
connectedevent carries noid— it is stream metadata, not a buffer position.
Quota impact
Each watcher alarm poll costs 1 API request toward your active quota window and counts toward metered overage billing. A watcher polling every 15 minutes consumes approximately 2,880 requests per 30 days. Listing watchers also make a live upstream fetch on each poll. Building and search watchers diff Borough’s cached index instead, so they avoid a live fetch on every cycle. If your quota (including overage headroom) is exhausted, the watcher is automatically paused: itsactive field flips to false and it stops polling. An open SSE stream
sees this on its next drain and emits a stopped event, then closes. There is no
dedicated quota event on the SSE stream, and the stopped payload does not say why the
watcher stopped — a quota pause, a PATCH with {"active": false}, and a delete all
produce the same event. To distinguish them, read GET /v1/watchers/{id} (a deleted
watcher returns 404) and check your usage. Reconnecting to a paused watcher returns
400 until you reactivate it.
Tier requirement: Business or Internal.
Choosing the Right Approach
Business and Internal tiers get live-first data automatically on
GET /v1/property/{id} and GET /v1/building/{id}: no SSE stream needed for single lookups. The regular endpoint returns "source": "live" when data is fetched fresh. See Data Freshness for details.