Open your browser's network tab on a Zillow property page and you'll see it: requests to /graphql/ carrying the data the page renders from — prices, history, the whole record. It looks like an API sitting right there, one fetch away. Developers find it, get their hopes up, and then spend a weekend discovering why it isn't the API they wanted.
This is a field guide to what's actually happening behind that endpoint — persisted queries, hashes that rotate on every deploy, and bot detection that scores endpoints independently — written by a team that keeps up with it in production. Not a copy-paste recipe (any recipe would be stale before you finished reading), but an honest map of the terrain, so you can decide whether you want to live on it.
It's GraphQL, but not a GraphQL API you can call
Zillow's front end is a GraphQL client. That's an implementation detail, not an invitation: there's no public schema, no docs, no auth story, no stability promise. The endpoint exists to serve zillow.com, and everything about how it's built assumes the caller is zillow.com.
The clearest sign of that is persisted queries. Instead of sending the full GraphQL query text, the client sends a reference to a query the server already knows:
This is a standard Apollo optimization — it saves bandwidth and lets the server allowlist exactly which queries it will run. For Zillow it does something else too: it means an outside caller can only invoke a query that already exists server-side, named by a hash. You can't ask for fields the front end doesn't ask for, and you can't ask at all once the hash you copied stops matching.
The hash rotates every time Zillow ships
That hash is derived from the exact text of the registered query. When Zillow's front-end team changes a query — adds a field, tweaks a fragment, refactors — the hash changes. Which they do continuously, because that's what shipping software looks like.
So the failure mode for anyone who hardcoded a hash is a slow, silent one: your integration works for days or weeks, then a routine Zillow deploy rotates the hash and your requests start coming back 400 Bad Request — the server no longer recognizes the persisted query. Nothing crashed. Your code is fine. The contract you were relying on simply stopped existing, and you find out from a dashboard going flat, not an exception.
Keeping up means re-capturing hashes from a live browser session whenever they rotate, per query type, forever. It's not hard the first time. It's that it never ends.
Then there's the part that returns 403
Suppose you've got a current hash. Now you meet PerimeterX (now HUMAN Security), Zillow's bot-detection layer. It scores every request on signals like your TLS fingerprint, the order of your headers, and how your session has behaved — and decides whether you're a browser or a script.
The non-obvious part, the one that costs people the most time: it scores the GraphQL endpoint independently of the pages. Your session can load property pages perfectly and still get a wall of 403s the moment it calls /graphql/. People burn hours here assuming the two must be linked — fix the headers that got the page working, and the GraphQL calls stay blocked, because the endpoint is judged on its own.
The two failure signatures are worth committing to memory, because they send you in opposite directions:
| Response | What it usually means | What it does NOT mean |
|---|---|---|
403 + captcha HTML |
Bot detection blocked this request's fingerprint/session | Your query or hash is wrong |
400 + JSON error |
The persisted-query hash has rotated — re-capture it | You're being blocked |
Empty priceHistory, no error |
Off-market payload genuinely omits it — needs the enrichment call | The request failed |
Reading a 400 as a block (and rotating proxies at it) or a 403 as a bad query (and re-capturing a perfectly good hash) are the two classic wrong turns. They feel identical from a stack trace; they're opposite problems.
And a session you can't just spin up cold
One more layer. The endpoint doesn't like cold connections — a fresh session firing straight at /graphql/ tends to get reset at the edge before it gets anywhere. The calls that succeed are ones made from a session that's already been warmed: it has established the right connection state and carries the cookies and signals a real browsing session would have by the time the page's own GraphQL calls fire.
Getting warming right — and keeping a pool of warmed sessions healthy, detecting when one has been silently flagged, and recycling it — is its own ongoing engineering problem. It's the kind of thing that works in a script on Tuesday and needs attention again by the following Monday.
The honest tally
None of these obstacles is insurmountable on its own. Stacked together, they describe a maintenance treadmill rather than a project with an end:
- Rotating hashes → re-capture per query type, indefinitely, or watch data silently disappear.
- Independent bot-scoring → the endpoint has to be defeated separately from the pages, and the rules change.
- Session warming → maintain and monitor a pool; detect silent flags; recycle.
- Proxies → residential IPs, rotated, because datacenter ranges are scored harshly.
- Off-market gaps → even a working call omits the sale date and price history on off-market homes; that data lives behind a different persisted query. We wrote about that specific gap in the sold data guide.
This is the same conclusion we reached in why building a Zillow scraper is harder than you think, viewed one layer down. The GraphQL endpoint isn't a shortcut around the scraping problem — it is the scraping problem, wearing an API's clothes.
What we do instead (so you don't have to)
Running Zillow data as a product means we treat every item above as our standing operational burden: we track hash rotations and re-capture them, maintain warmed session pools across the fingerprints bot detection currently tolerates, route through rotated residential proxies, and monitor the whole thing so that when Zillow changes something — and they do, regularly — it's an alert on our side, not a silent outage on yours.
What you get is the boring thing you actually wanted from that network tab: property data as clean JSON, behind a stable REST endpoint that doesn't rotate on you.
Skip the treadmill
The data behind Zillow's GraphQL layer, as stable JSON. Free tier, no credit card.
Get API KeyRelated reading
- Why building a Zillow scraper is harder than you think
- Zillow sold data API: the sale dates behind the GraphQL layer
- The unofficial Zillow API, explained
- Is scraping Zillow legal in 2026?
Final takeaway
Yes, Zillow runs on GraphQL. No, it isn't an API you can build on: persisted-query hashes rotate on every deploy, bot detection scores the endpoint on its own terms, and cold sessions get reset before they start. Treat that network-tab request as a look under the hood, not a door — and if you need the data behind it to just show up reliably, that's exactly the treadmill a hosted API exists to run for you.