Zillow API PHP example

Zillow API in PHP

Fetch Zillow property and listing data from PHP with the built-in cURL extension: a small reusable function, clear error handling and a batch call for lists of homes.

Base URL https://api.propertiesapi.com/v1 · auth header x-api-key. PropertiesAPI is an independent service that returns Zillow property and listing data as JSON, provided as-is.

How PHP fits

A lot of real estate websites run on PHP: WordPress themes for agents, Laravel back offices, and custom listing pages. The cURL extension ships with almost every PHP install, so the examples here need no Composer package at all.

The function below wraps one request: it sends the key in the x-api-key header, sets a long timeout, decodes the JSON into arrays and turns an error body into an exception that carries the API's error code. Your templates then only deal with the data array.

Do the lookup on the server, never from browser JavaScript, so the key stays private. A live lookup takes typically 8 seconds, up to about 20 seconds; for page views, store the result in your own database or a transient and refresh it on a schedule. Repeat lookups of the same home within 24 hours come from our cache and are free.

A reusable request function

Reads the key from the environment, sends the request and returns the decoded body. Errors become a PropertiesApiError with the HTTP status and the API's code.

PHP
<?php
const PAPI_BASE = 'https://api.propertiesapi.com/v1';

class PropertiesApiError extends RuntimeException {
    public function __construct(public int $status, public string $apiCode, string $message, public array $details = []) {
        parent::__construct("$status $apiCode: $message");
    }
}

function papi_request(string $method, string $path, array $query = [], ?array $body = null, int $timeout = 120): array {
    $url = PAPI_BASE . $path . ($query ? '?' . http_build_query($query) : '');
    $ch = curl_init($url);
    $headers = ['x-api-key: ' . getenv('PROPERTIESAPI_KEY')];
    if ($body !== null) {
        $headers[] = 'content-type: application/json';
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
    }
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
    ]);
    $raw = curl_exec($ch);
    if ($raw === false) throw new RuntimeException('network error: ' . curl_error($ch));
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $json = json_decode($raw, true) ?? [];
    if ($status >= 400) {
        $e = $json['error'] ?? [];
        throw new PropertiesApiError($status, $e['code'] ?? 'unknown', $e['message'] ?? 'request failed', $e['details'] ?? []);
    }
    return $json;
}

Look up one home

Pass exactly one of url, zpid or address. The response is ['data' => [...], 'meta' => [...]].

PHP
$body = papi_request('GET', '/zillow/property', ['url' => 'https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/']);
$home = $body['data'];

printf("%s: %s, list price %s\n", $home['address'], $home['homeStatus'], $home['price'] ?? 'not shown');
printf("Zestimate %s, rent Zestimate %s a month\n", $home['zestimate'] ?? 'not shown', $home['rentZestimate'] ?? 'not shown');
printf("%s beds, %s baths, %s sqft\n", $home['beds'] ?? '?', $home['baths'] ?? '?', $home['livingArea'] ?? '?');
echo $body['meta']['cached'] ? "from cache (free)\n" : "live lookup ({$body['meta']['credits']} credit)\n";

Handle the errors that matter

not_found and address_ambiguous are answers, not outages: show them to the user. Retry only rate limits and busy or upstream errors.

PHP
function find_home(string $address): ?array {
    for ($attempt = 0; $attempt < 4; $attempt++) {
        try {
            return papi_request('GET', '/zillow/property', ['address' => $address])['data'];
        } catch (PropertiesApiError $e) {
            if ($e->apiCode === 'not_found') return null;                   // not billed
            if ($e->apiCode === 'address_ambiguous') {
                // Zillow's closest match is a different home: ask the user, or look it up by link if it is the right one
                error_log('closest match: ' . ($e->details['candidate']['detailUrl'] ?? 'none'));
                return null;
            }
            if (in_array($e->status, [429, 502, 503, 504], true)) {
                sleep((int)($e->details['retry_after'] ?? 2 ** $attempt));
                continue;
            }
            throw $e;                                                      // 400 bad input, 401 key, 402 credits
        }
    }
    throw new RuntimeException("lookup failed after retries: $address");
}

$home = find_home('2640 Steiner St, San Francisco, CA 94115');

Batch a list of homes

POST /v1/zillow/batch with up to 50 items. Each result has its own ok flag, so one bad address doesn't sink the list. Give a batch a longer timeout than a single lookup.

PHP
$items = ['https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/', '742 Evergreen Ter, Springfield, IL 62704'];
$batch = papi_request('POST', '/zillow/batch', [], ['items' => $items], 320);

foreach ($batch['data'] as $r) {
    if ($r['ok']) {
        printf("#%d %s %s\n", $r['index'], $r['data']['address'], $r['data']['price'] ?? 'no price');
    } else {
        printf("#%d failed: %s\n", $r['index'], $r['error']['code']);
    }
}
printf("%d credits used, %d cache hits\n", $batch['meta']['credits'], $batch['meta']['cache_hits']);

PHP gotchas

  • Use the ?? operator on every field you print. A home that isn't for sale has no price, and some homes have no Zestimate.
  • http_build_query encodes the Zillow link and the address correctly; don't build the query string by hand.
  • In WordPress, run lookups from a cron hook or an admin action and save the result as post meta. Doing a live lookup on every page view makes pages slow and spends credits.
  • Set a client timeout of at least 120 seconds: an occasional lookup takes longer.

Zillow API and PHP: questions

Is there a Composer package for the Zillow API?+

Old Composer packages wrapped Zillow's retired ZWSID API and no longer work. PropertiesAPI needs no package: the cURL extension is enough, as shown above.

Can I call the API from a WordPress plugin?+

Yes, from PHP on the server. Keep the key in wp-config.php or an environment variable, and cache results in your own database so visitors never wait for a live lookup.

Why does json_decode give me null for zestimate?+

Because Zillow doesn't show a Zestimate for that home. Fields Zillow doesn't show for a home come back as null, so never read null as zero.

What does a lookup cost?+

Zillow links and addresses cost the same: 1 credit per successful lookup. Failed lookups and cache hits are free. You start with 50 free lookups.

Make your first call from PHP

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.