Skip to main content
Every response carries meta.coverage[], one entry per loaded market. Read it, and do not hard-code the market list. Then a market you did not know about starts to answer the day it lands.
The recorded shape of every field is on Which data is available where. GET /v1/coverage is the live answer for the host you call. Production serves three markets, as recorded on 2026-09-17: the Houston (Harris County) (hou, TX), the Phoenix metro (phx, AZ) and the Seattle metro (sea, WA). Which data is available where lists every dataset per market with its as-of date and parcel count. Counties, cities and ZIP codes lists every place inside them. meta.coverage[] on every response lists the markets loaded on the host you call, so a new market works the day it lands. Each county carries its own data_end beside the market’s. So you can see when one county runs behind.

What the counts are computed over

universe_kind says how wide the market’s own view is, and it is the caveat behind every number in it.

Ask outside a market

bbox is the rectangle that encloses every deal in the market. The API serves a point up to point_tolerance_miles outside it. The tolerance is 20 miles for a metro and 2 miles for a county market. Farther out, the API answers 422 outside_coverage. A viewport that intersects no market’s rectangle gets the same error. You get an error, not an empty page, on purpose. “No data here” and “nothing occurred here” are different answers. Only one of them is a reason for a user to stop looking. The API checks a place the same way. Before any query runs, it checks zip or city against the parcels of every loaded market. This applies to the deals list, the summary, the cells, the investor list, the Investorlift listings and the short-term rental parcels. For a ZIP or a city that no market carries, the API answers 422 outside_coverage. The error names the place and the loaded markets: zips_unknown lists the ZIPs, city the folded city. If a list has one unknown ZIP, the API refuses the whole list and serves nothing for the rest. So you never mistake a page for the full set. The city is the parcel’s postal city as the county records it, not a short-term rental jurisdiction of str.jurisdictions[]. One dataset can be narrower than its market. The lender registry covers the counties in lenders.counties. Counties, cities and ZIP codes lists them. For a ZIP, city or county outside them on the lender list, the API answers 422 outside_coverage and names the covered counties. The reason is the same: an empty page there reads as “nobody lends here”. The API does not clip a point on the lender list or on a lender’s loans to the circle. It resolves the geometry to the H3 res-8 cells whose centre lies inside the circle. Each cell is about a third of a square mile. So the edge of the geometry is a hex boundary, not the circle. The ranking row’s in_geometry says how many cells the geometry became and how many carry the lender (the ranking row). For a point outside every loaded market’s tolerance, the API answers 422 outside_coverage there too.

As-of dates

This data has no “today”. The API measures every window, every “days since” and every recency score from the market’s own as-of date. A null as-of date is an answer. It means the market has no published copy of that dataset. The matching blocks on every row are null. The routes and filters that need the dataset answer a 422 that says so: listings_unavailable, agents_unavailable, wholesale_unavailable, str_unavailable, parcels_unavailable, lenders_unavailable, or cash_sale_unavailable for parcel.sale_mortgage_measured: false. A null never means “nothing found”. auction_counted: false works the same way. The auction blocks and by_channel.auction on the deals summary are null. buys_at_auction, buys_reo and bought_auction_kind answer auction_unavailable. The history lake is the one dataset published for part of a market. A parcel outside parcel.history.zips answers history_unavailable, and the error names the ZIPs the lake does cover.

dataset_version

dataset_version is an integer per market. It changes at each rebuild of the market’s tables, and only then. A listing, wholesale or short-term rental refresh rolls it too. Put it in your cache key, and your cache invalidates itself at the next refresh. Keep a cached answer for at most 30 days. Drop it within a business day of a version change. Check the current version at least once a business day with GET /v1/dataset (not metered) or with the X-Dataset-Version header. The cache window is the rule. An example cache key:
The API also binds a pagination cursor to it. So a page request across a refresh answers 400 invalid_cursor, not a quietly wrong second page. build_run_id, registry_run and registry_version sit beside it for support questions. Do not cache on those. GET /v1/dataset returns the same coverage block without a query. The API does not meter it, so you read the block without a real call. GET /v1/coverage answers the same question for one place.