> ## Documentation Index
> Fetch the complete documentation index at: https://qwady.wiki/llms.txt
> Use this file to discover all available pages before exploring further.

# Streaming and Live Lookups

> Live listing lookups over SSE, plus watchers that monitor on a schedule

Borough offers two mechanisms for going beyond a plain cached read:

1. **Listing stream** (Pro+): a short-lived SSE stream that delivers cached data immediately, then one fresh live fetch
2. **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:

| Event    | Description                                                                                                         |
| -------- | ------------------------------------------------------------------------------------------------------------------- |
| `cached` | Immediate response from D1 cache                                                                                    |
| `live`   | Fresh data fetched live from the upstream source                                                                    |
| `error`  | Live fetch or parse could not complete; the payload includes a machine-readable error `code` such as `fetch_failed` |
| `done`   | Stream complete                                                                                                     |

```bash theme={null}
curl -N -H "Authorization: Bearer BOROUGH-..." \
  https://borough.qwady.app/v1/listing/4961849
```

```
event: cached
data: {"id":"4961849","price":3200,...}

event: live
data: {"id":"4961849","price":3150,...}

event: done
data: {"complete":true}
```

The cached event arrives immediately. The live event may take 2-5 seconds depending on upstream response time. If the live fetch fails, an `error` event is emitted with an error code and message, and the stream closes.

This is a short-lived stream: it completes after delivering cached + live data (typically under 10 seconds).

**Tier requirement:** Pro, Business, or Internal.

## Persistent Watchers

Watchers are long-lived subscriptions backed by Cloudflare Durable Objects. They support three watch types:

| Type       | Monitors              | Change Events                        |
| ---------- | --------------------- | ------------------------------------ |
| `listing`  | A single listing      | Price changes, status changes        |
| `building` | A building's listings | New listings added, listings removed |
| `search`   | A search query        | New listings matching your filters   |

### Creating a watcher

```bash theme={null}
curl -X POST https://borough.qwady.app/v1/watchers \
  -H "Authorization: Bearer BOROUGH-..." \
  -H "Content-Type: application/json" \
  -d '{"watchType":"listing","listingId":"4961849","pollInterval":600}'
```

### Poll intervals

Each poll costs one API request against your active quota window.

| Tier     | Minimum      | Default       |
| -------- | ------------ | ------------- |
| Business | 300s (5 min) | 900s (15 min) |
| Internal | 60s (1 min)  | 900s (15 min) |

### Streaming watcher changes

`GET /v1/watchers/{id}/stream` opens an SSE connection that emits change events:

| Event       | Description                                           |
| ----------- | ----------------------------------------------------- |
| `connected` | Stream opened, includes watcher metadata              |
| `change`    | A change was detected (price drop, new listing, etc.) |
| `heartbeat` | No changes detected this poll cycle                   |
| `reconnect` | Stream duration limit reached: reconnect              |
| `stopped`   | Watcher was deactivated                               |

```bash theme={null}
curl -N -H "Authorization: Bearer BOROUGH-..." \
  https://borough.qwady.app/v1/watchers/abc-123/stream
```

```
event: connected
data: {"watcherId":"abc-123","watchType":"listing","targetId":"4961849","pollInterval":600}

event: heartbeat
data: {"timestamp":"2026-02-19T12:00:00.000Z"}

event: change
data: {"eventType":"listing.price_decreased","listingId":"4961849","oldValue":"3200","newValue":"3150"}
```

### 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 a `reconnect` event and closes the stream:

```
event: reconnect
data: {"reason":"Stream duration limit reached. Please reconnect."}
```

Clients should automatically reconnect when receiving this event. Each SSE event includes an `id` field, but note that streams restart from the current state rather than replaying missed events.

### 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 and a `quota_exhausted` event is broadcast.

**Tier requirement:** Business or Internal.

## Choosing the Right Approach

| Scenario                                      | Recommended                                                     |
| --------------------------------------------- | --------------------------------------------------------------- |
| One-time fresh data for a listing             | Business tier regular endpoint, or listing stream (SSE) for Pro |
| Ongoing monitoring of a specific listing      | Watcher (listing type)                                          |
| Alert when new units appear in a building     | Watcher (building type)                                         |
| Alert when new listings match search criteria | Watcher (search type)                                           |
| Webhook delivery to your server               | Webhook subscription + watcher                                  |

<Note>
  **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](/borough/guides/data-freshness#live-first-data-business--internal) for details.
</Note>
