Watchlists and alerts
Watch homes by Zillow link, zpid or address. Each one is checked about once a day, and you get an event when its price, status, Zestimate, photos or rent Zestimate change: by signed webhook, a daily email, or the API.
Coming soon. Until watchlists launch, the endpoints below answer 404 watchlists_disabled.
Billing and limits
Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out.
| Plan | Watched homes |
|---|---|
| Free | Watchlists (coming soon): up to 5 homes |
| Starter | Watchlists (coming soon): up to 50 homes |
| Pro | Watchlists (coming soon): up to 200 homes |
| Business | Watchlists (coming soon): up to 1,000 homes |
| Scale | Watchlists (coming soon): up to 3,000 homes |
The limit counts every home on all your watchlists, paused ones included. Adding a home past it returns 403 watch_limit_reached before any lookup, so it costs nothing.
How checks work
- Watched homes are checked about once a day. There is no guaranteed time of day.
- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.
- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.
- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.
- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.
- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.
Events
| Type | When |
|---|---|
price_change | The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted. |
status_change | The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it. |
zestimate_change | The Zestimate moved by at least zestimate_change_pct (default 1). |
new_photos | The photo count went up. |
rent_change | The rent Zestimate moved by at least rent_change_pct (default 0: any change). |
Endpoints
Base https://api.propertiesapi.com/v1, with your key in the x-api-key header. The same watchlists are on your dashboard.
| Method | Path | What it does |
|---|---|---|
| GET | /watchlists | Your watchlists, each with its home_count. |
| POST | /watchlists | Create a watchlist. |
| GET | /watchlists/{id} | One watchlist with its homes: each home's zpid, address, last snapshot, last check and result. |
| PATCH | /watchlists/{id} | Change its name, thresholds or events, or pause it with { "paused": true }. |
| DELETE | /watchlists/{id} | Delete the watchlist and stop watching its homes. |
| POST | /watchlists/{id}/homes | Watch a home by url, zpid or address. It is looked up now (a normal lookup: 1 credit, free from the cache) to find the home and record its first snapshot. Or send { "items": [...] } with up to 25 links, zpids or addresses. An apartment building (a zillow.com/apartments/ link) is watched by its link: its zpid is null. If an address turns out to be a home the list already watches, the answer is 200 with meta.already_watched and that watch. |
| PATCH | /watchlists/{id}/homes/{homeId} | Pause or resume one home with { "paused": true | false }. |
| DELETE | /watchlists/{id}/homes/{homeId} | Stop watching a home. |
| GET | /watchlists/events | Recent events (newest first), ?limit= up to 500. |
| GET | /watchlists/settings | Your watched-home count and limit, whether checks are paused and why, the webhook URL and the email digest setting. |
| PATCH | /watchlists/settings | Set the webhook URL (https only, or null to remove it) and turn the daily email digest on or off. |
| POST | /watchlists/settings/secret | Create or rotate the webhook signing secret. It is shown in this response only. |
| GET | /watchlists/deliveries | The webhook delivery log: status, attempts, last HTTP status and error code for each event sent. |
| POST | /watchlists/resume | Resume checks that paused because your lookups or your spending limit ran out. |
curl "https://api.propertiesapi.com/v1/watchlists" -H "x-api-key: $PROPERTIESAPI_KEY" \
-H "content-type: application/json" \
-d '{"name":"Austin flips","price_change_pct":2}'curl "https://api.propertiesapi.com/v1/watchlists/wl_.../homes" -H "x-api-key: $PROPERTIESAPI_KEY" \
-H "content-type: application/json" \
-d '{"url":"https://www.zillow.com/homedetails/.../12345_zpid/"}'Webhooks
- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.
- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.
- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.
- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.
- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.
- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.
curl -X PATCH "https://api.propertiesapi.com/v1/watchlists/settings" -H "x-api-key: $PROPERTIESAPI_KEY" \
-H "content-type: application/json" \
-d '{"webhook_url":"https://example.com/hooks/propertiesapi"}'
curl -X POST "https://api.propertiesapi.com/v1/watchlists/settings/secret" -H "x-api-key: $PROPERTIESAPI_KEY" # shown once{
"id": "evt_0000000000000000",
"type": "watch.price_change",
"created_at": "2026-10-21T14:05:00.000Z",
"data": {
"watchlist_id": "wl_0000000000000000",
"home_id": "wh_0000000000000000",
"zpid": "0000000",
"address": "1600 Main St, Austin, TX 78701",
"detail_url": "https://www.zillow.com/homedetails/1600-Main-St-Austin-TX-78701/0000000_zpid/",
"old": 525000,
"new": 510000,
"change": -15000,
"change_pct": -2.86
}
}import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received (a string), secret: your whsec_ signing secret
export function verify(rawBody, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? "");
if (!m) return false;
if (Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false; // older than 5 minutes
const want = createHmac("sha256", secret).update(`${m[1]}.${rawBody}`).digest("hex");
return timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}Daily email
Turn on the email digest on your dashboard or with PATCH /watchlists/settings. You get at most one email a day, from login@propertiesapi.com, listing the changes found since the last one. No changes, no email.
Errors
| Status | Code | When |
|---|---|---|
| 400 | bad_param | A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse. |
| 400 | bad_webhook_url | The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which. |
| 402 | credits_exhausted | Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed. |
| 403 | account_required | The key isn't a dashboard key from an account. |
| 403 | watch_limit_reached | Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed. |
| 403 | too_many_watchlists | An account can have up to 20 watchlists. |
| 404 | not_found | No such watchlist or watched home on your account. |
| 404 | watchlists_disabled | Watchlists aren't available yet. |
| 409 | already_watched | That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed. |
Other errors are the same as for every v1 call. See errors.
Data is provided as-is, as shown on public Zillow pages at the time of each check, without any guarantee of accuracy or completeness. Zillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.