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:
- REST API — standard HTTP requests from any language
- MCP Server — native integration with Claude, Cursor, and other AI agents
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
| Field | Type | Description |
|---|---|---|
search | string | Free-text search query (e.g. "Los Angeles", "Miami Beach FL") |
addresses | string[] | Street addresses (e.g. ["123 Main St, City, ST 12345"]) |
zipcodes | int[] | US ZIP codes (e.g. [90210, 90211]) |
zpids | int[] | Zillow Property IDs |
urls | string[] | Zillow listing URLs or search page URLs |
type | string | all (default), sale, rent, sold, fsbo. Works on its own for zipcodes searches — no need to also set property_type. |
property_type | string | Filter by property type: house, condo, townhouse, land, apartment, manufactured, multi_family |
price_min | int | Minimum price filter (USD) |
price_max | int | Maximum price filter (USD) |
max_items | int | Ceiling on results returned (default 200). Lowered automatically to your plan's per-request limit — see Per-request limits below. |
webhook_url | string | Public http(s) URL to receive a POST callback when the job finishes, instead of polling. See Webhooks below. |
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.
| Plan | Max properties per request |
|---|---|
| Free | 5 |
| Pro | 10 |
| Ultra | 25 |
| Mega | 50 |
| Volume plans | higher — 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:
POST /v1/propertiesreturns ajob_idinstantly (under 100ms)- Poll
GET /v1/results/{job_id}every 10-15 seconds - When
statusis"complete", results are in theresultsarray
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.
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
| Field | Type | Description |
|---|---|---|
zpid | int | Zillow Property ID |
street_address | string | Street address |
city, state, zipcode | string | Location |
latitude, longitude | float | Geocoordinates |
price | int | Listing price (USD) |
last_sold_price | int | Last recorded sale price |
last_sold_date | string | Date of the last recorded sale (ISO YYYY-MM-DD) |
zestimate | int | Zillow's estimated value. Null for active listings (FOR_SALE, FOR_RENT, PENDING, CONTINGENT) — only populated for sold/off-market homes. |
rent_zestimate | int | Estimated monthly rent |
bedrooms, bathrooms | int/float | Bed and bath count |
living_area | int | Interior sqft |
lot_size | float | Lot size (sqft) |
year_built | int | Year constructed |
property_type | string | SINGLE_FAMILY, CONDO, TOWNHOUSE, etc. |
home_status | string | FOR_SALE, SOLD, FOR_RENT, OTHER |
description | string | Full listing description |
hoa_fee | float | Monthly HOA fee |
days_on_zillow | int | Days listed |
page_view_count | int | Zillow page views |
favorite_count | int | Times favorited |
price_history | array | Price events with date, event, price, source |
tax_history | array | Tax records with year, tax paid, assessed value |
listing_agent | object | Agent name, phone, email, company |
listing_broker | string | Brokerage name |
listing_owner | object | FSBO 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_schools | array | Schools with rating, grades, distance |
comps | array | Nearby 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_urls | array | Property photo URLs |
Error Handling
| HTTP Code | Meaning |
|---|---|
200 | Success (check results and errors arrays) |
400 | No valid input provided |
401 | Invalid or missing API key |
429 | Rate 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
MCP Tools
Property Data
| Tool | Description |
|---|---|
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
| Tool | Description |
|---|---|
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.
- Address list → enriched Google Sheet — read addresses, enrich with price/Zestimate/last-sold, write back.
- ZIP new-listing watcher → Slack — daily diff of active listings in a ZIP, posts new ones.
- Deal-flow monitor — watches your ZIPs daily, scores each new listing by $/sqft vs the ZIP median, logs to Google Sheets (deduped by ZPID), and flags below-market listings in Slack. Polling loop, retries, and error alerts included. Also in the official n8n template gallery for one-click import.
Plans
| Plan | Price | Properties/mo | Rate Limit |
|---|---|---|---|
| Free | $0 | 50 | 5/min |
| Pro | $9.99/mo | 3,333 | 20/min |
| Ultra | $29.99/mo | 10,000 | 60/min |
| Mega | $99.99/mo | 50,000 | 120/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.