Zillow API JavaScript tutorial

Zillow API in JavaScript

Fetch listing price, status, Zestimate, rent Zestimate, photos and price history from a Zillow link or an address, from Node.js or your own API route. Then batch a whole list.

Base URL https://api.propertiesapi.com/v1 · auth header x-api-key. Zillow's own API needs Bridge approval. This tutorial uses PropertiesAPI, an independent service that returns Zillow property and listing data as JSON, provided as-is.

1. Set your key

Node 18+ has fetch built in. Keep the key on the server. Never ship it to the browser.

Shell
export PROPERTIESAPI_KEY="your_key_here"

2. Look up one Zillow link

Send any zillow.com/homedetails link as ?url=, or a plain address as ?address=. A live lookup takes typically 8 seconds, up to about 20, so set a timeout. The body is { data, meta }.

JavaScript (Node)
const BASE = "https://api.propertiesapi.com/v1";
const headers = { "x-api-key": process.env.PROPERTIESAPI_KEY };

const url = new URL(`${BASE}/zillow/property`);
url.searchParams.set("url", "https://www.zillow.com/homedetails/130-Water-St-APT-12D-New-York-NY-10005/2064142765_zpid/");
// or: url.searchParams.set("address", "1600 Main St, Austin, TX 78701");

const res = await fetch(url, { headers, signal: AbortSignal.timeout(120_000) });
if (!res.ok) throw new Error((await res.json()).error.message);
const { data: home, meta } = await res.json();

console.log(home.address, home.homeStatus, res.headers.get("x-cache"), `${meta.credits} credit(s)`);
console.log(`Price $${home.price?.toLocaleString()} · Zestimate $${home.zestimate?.toLocaleString()} · rent $${home.rentZestimate}/mo`);
console.log(`${home.beds} beds · ${home.baths} baths · ${home.livingArea} sqft · ${home.daysOnZillow} days on Zillow`);

3. Type the response

Any field Zillow doesn't show for a property comes back as null. Don't treat null as zero. The main fields (the docs list all of them):

TypeScript
type ZillowProperty = {
  zpid: string | null;
  detailUrl: string | null;
  address: string | null;
  city: string | null; state: string | null; zipcode: string | null;
  latitude: number | null; longitude: number | null;
  homeStatus: string | null;        // "FOR_SALE", "FOR_RENT", "RECENTLY_SOLD", "OFF_MARKET", ...
  price: number | null;
  zestimate: number | null;
  rentZestimate: number | null;
  beds: number | null; baths: number | null; livingArea: number | null;
  lotSize: number | null; yearBuilt: number | null; homeType: string | null;
  daysOnZillow: number | null;
  imgSrc: string | null;
  photos: string[];
  priceHistory: { date: string | null; event: string | null; price: number | null }[];
  taxHistory: { year: number | null; taxPaid: number | null; value: number | null }[];
  brokerName: string | null; agentName: string | null;
  // ...and more: see /docs/endpoints/property
};

type PropertyResponse = {
  data: ZillowProperty;
  meta: { credits: number; cached: boolean; cache_key: string; fetched_at: string; took_ms: number };
};

4. Handle errors and retries

A 404 means we couldn't find a Zillow property, and failed lookups aren't billed. Retry timeouts and 5xx with a backoff (a 503 carries retry-after).

TypeScript
async function lookup(params: { url: string } | { address: string }): Promise<ZillowProperty | null> {
  const u = new URL(`${BASE}/zillow/property`);
  for (const [k, v] of Object.entries(params)) u.searchParams.set(k, v);

  for (let attempt = 0; attempt < 4; attempt++) {
    let r: Response;
    try {
      r = await fetch(u, { headers, signal: AbortSignal.timeout(120_000) });
    } catch {
      continue; // timed out: the live lookup took too long, try again
    }
    if (r.status === 404) return null;                  // no matching Zillow property
    if (r.status >= 500) {
      const wait = Number(r.headers.get("retry-after")) || 2 ** attempt;
      await new Promise((ok) => setTimeout(ok, wait * 1000));
      continue;
    }
    if (!r.ok) throw new Error((await r.json()).error.code); // 401 missing/invalid_api_key, 400 bad_param
    return ((await r.json()) as PropertyResponse).data;
  }
  throw new Error("lookup failed after retries");
}

5. Call it from your own API route

Proxy through your server so the key stays secret. A Next.js route handler, for example:

app/api/home/route.ts
export async function GET(req: Request) {
  const link = new URL(req.url).searchParams.get("url") ?? "";
  const u = new URL("https://api.propertiesapi.com/v1/zillow/property");
  u.searchParams.set("url", link);

  const r = await fetch(u, {
    headers: { "x-api-key": process.env.PROPERTIESAPI_KEY! },
    signal: AbortSignal.timeout(120_000),
  });
  const body = await r.json();
  return Response.json(r.ok ? body.data : body.error, { status: r.status });
}

6. Batch links or addresses

POST /v1/zillow/batch takes up to 50 Zillow URLs or addresses per request. Results come back in data, in order, each with ok. Lookups run live, so give a full batch a long timeout. Billed per successful live lookup; cache hits are free.

JavaScript (Node)
import { readFile, writeFile } from "node:fs/promises";

const inputs = (await readFile("zillow-links.txt", "utf8")).split("\n").map((l) => l.trim()).filter(Boolean);
const found = [], failed = [];

for (let i = 0; i < inputs.length; i += 50) {
  const r = await fetch(`${BASE}/zillow/batch`, {
    method: "POST",
    headers: { ...headers, "content-type": "application/json" },
    body: JSON.stringify({ items: inputs.slice(i, i + 50) }), // links or addresses
    signal: AbortSignal.timeout(320_000),
  });
  if (!r.ok) throw new Error((await r.json()).error.message);
  for (const item of (await r.json()).data) (item.ok ? found : failed).push(item);
}

await writeFile("homes.json", JSON.stringify(found.map((x) => x.data), null, 2));
console.log(`${found.length} found, ${failed.length} failed (not billed)`);

Zillow API JavaScript questions

Is there an official Zillow API for Node.js?+

No. npm packages such as node-zillow wrapped the legacy ZWSID API, which was retired in 2021. Zillow's current APIs go through Bridge Interactive by application.

Is this Zillow's official API?+

No. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow. It returns what the public Zillow page shows, provided as-is, with no guarantee of accuracy or completeness.

Can I call the API from the browser?+

Don't: it would expose your key. Call it from your server or a serverless function and pass the result to the browser.

Is there a JavaScript SDK?+

A TypeScript SDK is planned. Today the API is plain HTTPS and JSON, so fetch is all you need.

How many lookups do I get for free?+

50 free lookups, once, with no card. Paid plans (lookups a month): Starter $29/mo (2,500); Pro $79/mo (7,500); Business $199/mo (20,000); Scale $499/mo (65,000). Overage is your plan's own per-lookup rate ($11.60 per 1,000 on Starter, down to $7.68 on Scale). Repeat lookups of the same home within 24 hours are free. Failed lookups aren't billed.

Your first call in JavaScript

50 free lookups, no card. Join the list; keys go out as we open accounts.

Zillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.