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
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 requests/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 |
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 | Max results, 1-1000 (default 200). See pagination note below. |
search, addresses, zipcodes, zpids, or urls is required. You can combine multiple in one request.Pagination (search & ZIP code queries)
Zillow's search results pages return ~41 listings per page. For search, zipcodes, and search-page urls inputs, APIllow automatically paginates up to 5 pages (~205 results) per query to reach your requested max_items. Requests above 205 will be capped:
| max_items | Pages fetched | Expected results |
|---|---|---|
| 1-41 | 1 | up to max_items |
| 42-82 | 2 | up to max_items |
| 83-123 | 3 | up to max_items |
| 124-164 | 4 | up to max_items |
| 165-205 | 5 | up to max_items |
| 206+ | 5 (capped) | ~205 |
This cap does not apply to addresses, zpids, or listing-page urls — those can go up to 1,000 per request.
To get more than 205 results from a region, split into multiple queries by ZIP code, neighborhood, 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 3-5 seconds - When
statusis"complete", results are in theresultsarray
Results are typically ready in 3-10 seconds depending on the number of properties requested.
GET /v1/results/{job_id}
Poll for results from an async batch job. The status field will be "processing", "complete", or "failed".
curl https://api.apillow.co/v1/results/YOUR_JOB_ID \ -H "X-API-Key: your_key"
Poll every 5-10 seconds until status is "complete".
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.
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. Quota resets on the 1st of each calendar month (UTC).
Overages. Paid plans bill at overage_rate_per_1k per 1,000 requests beyond monthly_limit. 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 |
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 |
nearby_schools | array | Schools with rating, grades, distance |
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.
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).
Plans
| Plan | Price | Requests/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.