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 6 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/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/");
// or: url.searchParams.set("address", "2640 Steiner St, San Francisco, CA 94115");

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?+

Yes: the official Node.js and TypeScript client installs with npm from this site (see the SDKs page; it isn't on the npm registry). The API is also plain HTTPS and JSON, so fetch works without it.

How many lookups do I get for free?+

50 free lookups, once, with no card and no approval. Paid plans (lookups a month): Starter $29/mo (4,000); Pro $79/mo (11,500); Business $199/mo (30,000); Scale $499/mo (80,000). Overage is your plan's rate per 1,000 lookups, shown on each plan. Or pay as you go: $10 per 1,000 lookups, prepaid, in packs from $20 for 2,000 lookups up to $250 for 25,000 lookups. No subscription; credits last 12 months, and on a plan they are used after its included lookups. Buying a pack ends the free trial: from then on lookups use your prepaid credits. 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, no approval.

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