Documentation

Everything you need to get Zillow property data into your app, pipeline, or AI agent.

Overview

APIllow is a Zillow property data API. Send a city name, ZIP code, property ID, or Zillow URL and get back 50+ structured data fields per property — address, price, Zestimate, beds/baths, price history, tax records, school ratings, agent info, and photos.

Two ways to use it:

Background reading: does Zillow have an API in 2026? explains where this data comes from, and the Zestimate API guide covers pulling Zestimates by address (including the null-Zestimate gotcha on active listings).

Authentication

All API requests require an API key passed via the X-API-Key header:

curl -H "X-API-Key: your_key" https://api.apillow.co/v1/properties

Get a free key (50 properties/month) by signing up on the homepage or through the MCP server.

Quick Start

# Search for properties in Los Angeles
curl -X POST https://api.apillow.co/v1/properties \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_key" \
  -d '{"search": "Los Angeles", "type": "sale", "max_items": 5}'

Python

import requests

resp = requests.post(
    "https://api.apillow.co/v1/properties",
    headers={"X-API-Key": "your_key"},
    json={"search": "Los Angeles", "type": "sale", "max_items": 5}
)
for r in resp.json()["results"]:
    p = r["property"]
    print(f"{p['street_address']}, {p['city']} — ${p['price']:,}")

JavaScript

const resp = await fetch("https://api.apillow.co/v1/properties", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-API-Key": "your_key" },
  body: JSON.stringify({ search: "Los Angeles", type: "sale", max_items: 5 })
});
const data = await resp.json();
data.results.forEach(r => console.log(r.property.street_address, r.property.price));

POST /v1/properties

Get property data from Zillow. Accepts multiple input types in a single request.

Request Body

FieldTypeDescription
searchstringFree-text search query (e.g. "Los Angeles", "Miami Beach FL")
addressesstring[]Street addresses (e.g. ["123 Main St, City, ST 12345"])
zipcodesint[]US ZIP codes (e.g. [90210, 90211])
zpidsint[]Zillow Property IDs
urlsstring[]Zillow listing URLs or search page URLs
typestringall (default), sale, rent, sold, fsbo. Works on its own for zipcodes searches — no need to also set property_type.
property_typestringFilter by property type: house, condo, townhouse, land, apartment, manufactured, multi_family
price_minintMinimum price filter (USD)
price_maxintMaximum price filter (USD)
max_itemsintCeiling on results returned (default 200). Lowered automatically to your plan's per-request limit — see Per-request limits below.
webhook_urlstringPublic http(s) URL to receive a POST callback when the job finishes, instead of polling. See Webhooks below.
At least one of search, addresses, zipcodes, zpids, or urls is required. You can combine multiple in one request.

Per-request limits

Every request returns at most a fixed number of properties, set by your plan. The limit applies to every input type: search, zipcodes, and search-page urls as well as addresses, zpids, and listing-page urls.

PlanMax properties per request
Free5
Pro10
Ultra25
Mega50
Volume planshigher — contact us

Searches — max_items is a ceiling. Ask for more than your plan's limit and the request still succeeds, with max_items lowered to the limit.

Explicit lists — sending more addresses, zpids, or listing urls than the limit returns a 400 so you always know exactly what was looked up; nothing is silently dropped.

Since billing is per property, splitting a list across requests costs nothing extra. To cover more of a region than one request returns, split into multiple queries by ZIP code, property type, or price range.

Async Flow

All requests are async for maximum speed and reliability:

  1. POST /v1/properties returns a job_id instantly (under 100ms)
  2. Poll GET /v1/results/{job_id} every 10-15 seconds
  3. When status is "complete", results are in the results array

Results are typically ready in 3-10 seconds depending on the number of properties requested. Prefer setting webhook_url over polling — see Webhooks.

GET /v1/results/{job_id}

Poll for results from an async batch job. The status field will be "processing", "complete", or "failed". Results are only readable by the API key that submitted the job; any other key gets a 404, same as an unknown job_id.

curl https://api.apillow.co/v1/results/YOUR_JOB_ID \
  -H "X-API-Key: your_key"

Poll every 10-15 seconds until status is "complete", or set webhook_url on the original request and skip polling — see Webhooks.

CSV export

Add ?format=csv to download the successful properties as a flat spreadsheet — one row per property, ~42 columns (address, price, beds/baths, Zestimate, agent contact, and more).

curl "https://api.apillow.co/v1/results/YOUR_JOB_ID?format=csv" \
  -H "X-API-Key: your_key" -o results.csv

CSV is only emitted once the job is "complete". While it's still processing or has failed, the normal JSON status is returned regardless of format, so your existing poll loop keeps working — just request format=csv on the final poll, or every poll and check the response Content-Type.

Nested fields that don't fit a flat cell (price_history, tax_history, nearby_schools, comps) are written as JSON strings in a single column each, so no data is lost. Feature lists are joined with "; ". The image_urls array is summarized as primary_image_url + image_count.

Webhooks

Set webhook_url on the original POST /v1/properties request to get a single POST callback when the job finishes, instead of polling. It must be a public http(s) host — no loopback, private, or link-local addresses — or the request is rejected with 400.

Callback headers:

X-Apillow-Event: job.completed
User-Agent: apillow-webhook/1

Callback body — no results in the body, fetch them from results_url:

{
  "job_id": "4a021afd-c429-...",
  "status": "complete",
  "result_count": 5,
  "error_count": 0,
  "results_url": "https://api.apillow.co/v1/results/4a021afd-c429-...",
  "completed_at": "2026-09-10T14:02:31+00:00"
}

Up to 3 delivery attempts (immediately, then +30s, then +4min), 10s timeout per attempt, any 2xx response counts as delivered, redirects are not followed.

Delivery status is reported on the job as webhook: {status, attempts, last_status, delivered_at} — status is "pending", "delivered", or "failed"; null if no webhook_url was submitted.

GET /v1/usage

Return your current monthly usage, plan, and quota. Useful for client-side budget tracking and dashboards.

Usage is counted per property returned, not per API call. A search that returns 10 properties uses 10 units of your monthly quota; an address lookup that returns 1 property uses 1. Calls that return nothing are free. All quota, limit, and overage figures below are in properties.
curl https://api.apillow.co/v1/usage \
  -H "X-API-Key: your_key"

Sample response:

{
  "plan": "ultra",
  "monthly_limit": 10000,
  "used_this_month": 3354,
  "remaining": 6646,
  "overage_count": 0,
  "overage_rate_per_1k": 5.00,
  "billing_period_start": "2026-05-01T00:00:00+00:00",
  "billing_period_end": "2026-06-01T00:00:00+00:00"
}

Reservation-inclusive counting. used_this_month counts in-flight jobs at their submitted max_items, then settles downward as jobs complete and unused portions are refunded. This matches the number the server uses internally to enforce the monthly limit.

Period. Paid plans reset on the subscription's monthly billing date; free keys reset on the 1st of each calendar month (UTC).

Overages. Paid plans bill at overage_rate_per_1k per 1,000 properties returned beyond monthly_limit ($0.005 per property). Free plans and grandfathered hard-capped accounts return overage_rate_per_1k: null and never exceed the limit.

Authentication is required, but the monthly quota is not enforced on this endpoint — you can always retrieve your own usage even after exceeding the limit.

Calling /v1/usage does not itself consume quota. This endpoint reads your usage counter but never increments it, so it's safe to poll for client-side budget tracking. Standard per-plan rate limits still apply.

Input Types

Search by city

{ "search": "Austin TX", "type": "sale", "max_items": 10 }

Look up by street address

{ "addresses": ["22525 Shaker Blvd, Shaker Heights, OH 44122"] }

Multiple addresses

{ "addresses": [
    "123 Main St, Cleveland, OH 44101",
    "456 Oak Ave, Lakewood, OH 44107"
  ]
}

Search by ZIP code

{ "zipcodes": [90210, 90211], "type": "sale" }

Look up by ZPID

{ "zpids": [20794780, 20637558] }

Get data by URL

{ "urls": ["https://www.zillow.com/homedetails/123-Main-St/12345_zpid/"] }

Search page URL

{ "urls": ["https://www.zillow.com/beverly-hills-ca/"], "max_items": 20 }

Mix and match

{ "search": "Miami", "zipcodes": [33139], "type": "rent", "max_items": 50 }

Filter by property type and price

{ "zipcodes": ["80831"], "type": "sale", "property_type": "land", "price_max": 50000 }

Supported property types: house, condo, townhouse, land, apartment, manufactured, multi_family. Price filters apply at the search level, reducing unnecessary API calls.

Response Format

{
  "job_id": "4a021afd-c429-...",
  "status": "complete",
  "results": [
    {
      "url": "https://www.zillow.com/homedetails/.../20794780_zpid/",
      "success": true,
      "zpid": 20794780,
      "elapsed_seconds": 2.8,
      "property": {
        "street_address": "1735 N Fuller Ave APT 425",
        "city": "Los Angeles",
        "price": 379888,
        "zestimate": 374200,
        ... 50+ fields ...
      }
    }
  ],
  "errors": []
}

Data Fields

FieldTypeDescription
zpidintZillow Property ID
street_addressstringStreet address
city, state, zipcodestringLocation
latitude, longitudefloatGeocoordinates
priceintListing price (USD)
last_sold_priceintLast recorded sale price
last_sold_datestringDate of the last recorded sale (ISO YYYY-MM-DD)
zestimateintZillow's estimated value. Null for active listings (FOR_SALE, FOR_RENT, PENDING, CONTINGENT) — only populated for sold/off-market homes.
rent_zestimateintEstimated monthly rent
bedrooms, bathroomsint/floatBed and bath count
living_areaintInterior sqft
lot_sizefloatLot size (sqft)
year_builtintYear constructed
property_typestringSINGLE_FAMILY, CONDO, TOWNHOUSE, etc.
home_statusstringFOR_SALE, SOLD, FOR_RENT, OTHER
descriptionstringFull listing description
hoa_feefloatMonthly HOA fee
days_on_zillowintDays listed
page_view_countintZillow page views
favorite_countintTimes favorited
price_historyarrayPrice events with date, event, price, source
tax_historyarrayTax records with year, tax paid, assessed value
listing_agentobjectAgent name, phone, email, company
listing_brokerstringBrokerage name
listing_ownerobjectFSBO seller contact: display_name, phone. Only on active for-sale-by-owner listings where Zillow shows the seller's phone; null otherwise. Not verified ownership.
nearby_schoolsarraySchools with rating, grades, distance
compsarrayNearby comparable homes: zpid, address, price, bedrooms, bathrooms, living_area, home_status, url, latitude, longitude. No sold date on a comp; for a FOR_RENT comp, price is the monthly rent. To get sold dates and Zestimates for comps, fetch them in one batch by their url.
image_urlsarrayProperty photo URLs

Error Handling

HTTP CodeMeaning
200Success (check results and errors arrays)
400No valid input provided
401Invalid or missing API key
429Rate limit or monthly quota exceeded

Individual property failures appear in the errors array with details:

{
  "url": "https://www.zillow.com/homedetails/invalid/0_zpid/",
  "success": false,
  "error": "HTTP 404",
  "attempts": 3
}

MCP Server Installation

The MCP server lets AI agents (Claude, Cursor, Windsurf, etc.) access Zillow property data directly as tools. New to it? Start with the MCP server guide.

Install

pip install apillow-mcp

Claude Desktop

Add to ~/.claude/claude_desktop_config.json:

{
  "mcpServers": {
    "apillow": {
      "command": "apillow-mcp",
      "env": {
        "APILLOW_API_KEY": "your_key"
      }
    }
  }
}

Claude Code

claude mcp add apillow -- apillow-mcp
No API key yet? The agent can create one for you — just ask it to sign up.

MCP Tools

Property Data

ToolDescription
search_properties(query, type, max_items)Search by city or area name
search_by_zip(zipcodes, type, max_items)Search by ZIP codes
get_property(zpid)Look up by Zillow Property ID
get_property_by_url(url)Get data from a Zillow listing URL
get_property_by_address(address)Look up by street address
get_properties_by_addresses(addresses)Bulk address lookup

Account Management

ToolDescription
signup(email)Create free account, get API key instantly
check_usage()See current month's usage, quota, and plan
upgrade_plan(plan, email)Get a Stripe checkout URL to upgrade

Zero-Browser Signup

Users can sign up and start querying entirely through the AI agent:

# User asks their AI agent:
"Sign me up for Apillow with user@example.com"

# Agent calls signup("user@example.com") and gets:
{
  "api_key": "zs_abc123...",
  "plan": "free",
  "monthly_limit": 50
}

# Agent can immediately query:
"Find 3-bedroom homes under $500K in Austin TX"

# When they hit the limit, agent suggests upgrading:
"You've used 50/50 requests. Upgrade to Pro?"
# Agent calls upgrade_plan("pro") → returns Stripe URL

The only step requiring a browser is the Stripe payment page (PCI compliance).

n8n Templates

Ready-to-import n8n workflows that call the API — no custom node needed. Store your key as a Header Auth credential (x-api-key) named "APIllow", import the JSON (Workflows → Import from File), and select the credential. Walkthrough: Zillow property data in n8n.

Plans

PlanPriceProperties/moRate Limit
Free$0505/min
Pro$9.99/mo3,33320/min
Ultra$29.99/mo10,00060/min
Mega$99.99/mo50,000120/min

Billing API

POST /billing/signup

Create a free API key. No authentication required.

{ "email": "user@example.com" }

GET /billing/usage?api_key=your_key

Check current usage and quota.

POST /billing/checkout

Generate a Stripe Checkout URL for upgrading to a paid plan.

{ "email": "user@example.com", "plan": "pro" }

Returns a checkout_url — open in browser to complete payment.

POST /billing/webhook

Stripe webhook endpoint for subscription events. Configured in your Stripe Dashboard.