{
  "openapi": "3.1.0",
  "info": {
    "title": "PropertiesAPI Zillow API",
    "version": "1.0.0-preview",
    "summary": "Zillow property and listing data as JSON, from a Zillow URL or an address.",
    "description": "Send a Zillow property URL or a US address. Get back { data, meta }: data has the listing price and status, Zestimate, rent Zestimate, home facts, photo URLs, days on Zillow, price history, tax history, schools, broker and agent. Live lookups typically take 8 seconds, up to about 20 (measured; set a client timeout of at least 120 seconds); repeat lookups of the same home within 24 hours come from cache and are free (x-cache: HIT). Errors aren't billed. Fields Zillow doesn't show for a property are null.\n\nResponse headers: every response has x-api-version and cache-control; authenticated requests get x-ratelimit-limit and x-ratelimit-remaining; account keys get the x-credits-* headers (x-credits-prepaid-remaining (always; 0 when there are none), x-spend-limit-usd and x-credits-overage-remaining with a spending limit); errors with a wait get retry-after; 402 gets link. See components.headers.\n\nRetries: Codes with a retry-after wait (429 rate_limited, daily_share_exhausted and env_key_daily_cap; 503 upstream_busy, upstream_unavailable, store_unavailable, live_lookups_capped, state_unavailable and storage_full) can be retried after that many seconds (not upstream_blocked: its 502 carries retry-after too, but collection is paused, so it isn't retried automatically); the daily caps wait until midnight UTC. Retry 500, other 502 codes and 504 with exponential backoff. Don't retry other 4xx errors without changing the request. Other 503 codes mean live lookups are off on our side: retrying soon won't help. Errors aren't billed.\n\nData is provided as-is, without any guarantee of accuracy or completeness. Not for FCRA uses (credit, insurance, employment or tenant screening).\n\nZillow is a trademark of Zillow, Inc. PropertiesAPI is an independent service and is not affiliated with, endorsed by or sponsored by Zillow.",
    "contact": {
      "name": "PropertiesAPI",
      "url": "https://propertiesapi.com/waitlist"
    }
  },
  "servers": [
    {
      "url": "https://api.propertiesapi.com/v1",
      "description": "v1 (preview)"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Zillow",
      "description": "Zillow property and listing lookups by URL or address."
    },
    {
      "name": "Watchlists",
      "description": "Watch homes, checked about once a day; events by webhook, email digest or the API. Example webhook body: {\"id\":\"evt_0000000000000000\",\"type\":\"watch.price_change\",\"created_at\":\"2026-10-21T14:05:00.000Z\",\"data\":{\"watchlist_id\":\"wl_0000000000000000\",\"home_id\":\"wh_0000000000000000\",\"zpid\":\"15073305\",\"address\":\"2640 Steiner St, San Francisco, CA 94115\",\"detail_url\":\"https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/\",\"old\":525000,\"new\":510000,\"change\":-15000,\"change_pct\":-2.86}}"
    }
  ],
  "paths": {
    "/zillow/property": {
      "get": {
        "operationId": "getZillowProperty",
        "summary": "Zillow property lookup",
        "description": "One property by Zillow URL or address: listing price and status, Zestimate, rent Zestimate, beds, baths, living area, lot, year built, home type, photo URLs, days on Zillow, price history, tax history, schools, broker and agent.\n\nBilling: 1 credit per successful live lookup, by Zillow link or address. Repeat lookups of the same home within 24 hours come from cache and are free. Errors aren't billed.\n\n- Send exactly one of url, zpid or address. Sending none returns 400 missing_param; sending several returns 400 bad_param.\n- The body is { data, meta }. data is the property; meta has credits, cached, cache_key, fetched_at, source, release, took_ms, completeness and, when there is something to check, warning. For some answers meta.source_age is \"approximate\": the source served a copy it may have collected earlier, so fetched_at (when we received it) can be a few hours later than when the data was collected; those answers stay in our cache for a shorter time.\n- meta.source.note says where the data comes from: the property's public Zillow page. You're billed once per successful live lookup.\n- The x-cache response header is HIT when the result came from the 24-hour cache (free, no live lookup) and MISS when it was fetched live.\n- A live lookup typically takes 6 seconds, up to about 20. Set a client timeout of at least 120 seconds: an occasional lookup takes longer.\n- Fields Zillow doesn't show for a property come back as null. Don't treat null as zero.\n- A page with no listing data at all (no price, beds, baths, size, available units or Zestimate; for example a building with no units listed) returns 404 not_found, not billed.\n- Which fields are filled can depend on how the home was first looked up (by link, zpid or address), and the 24-hour cache keeps that first answer: for example the tax amounts, the tax rate and the view and favorite counts. When PropertiesAPI has collected the same home both ways within 24 hours, the result carries both sets (each value for at most a day after it was collected). meta.completeness says, for this response, which of the commonly missing fields were supplied, came back null or came back as an empty list.\n- daysOnZillow is Zillow's own day count and is usually set for sold and off-market homes too. For days on market use calculated.days_on_market, which is null unless the home is listed.\n- With raw=true, data.raw is the unmapped source record, untouched: values PropertiesAPI corrects in data (for example priceHistory[].priceChangeRate from some sources) are left as the source sent them there. It is unstable: its keys and casing depend on which source served the lookup, so they differ between lookups of the same home and change without notice. Don't parse it.\n- Every response carries x-api-version and cache-control; authenticated requests also get the rate-limit headers, and account keys the credit headers (see Headers). Errors carry the same headers, plus retry-after when there is a wait to honor.\n- price is the listing price (monthly rent for rentals). zestimate and rentZestimate are Zillow's own estimates, returned only when Zillow shows them. PropertiesAPI doesn't make estimates of its own.\n- data.calculated has figures calculated by PropertiesAPI from the returned fields; not Zillow estimates: price and Zestimate per square foot, the rent-to-price ratio and gross yield, price vs Zestimate, the last sale and the change since it, days on market, and price drops. Each is null when its inputs are missing. The formulas are under Calculated fields.\n- A building link (zillow.com/apartments/... or zillow.com/b/...) returns the building: address, photos and schools are the building listing's, zpid is null (use detailUrl as the building's id), and price, beds, baths, livingArea and unit come from its lowest-priced unit available now (no move-in date, or one on or before the lookup date; if none is available yet, its lowest-priced unit). The other units aren't returned yet.\n- photos is a list of image URLs. priceHistory, taxHistory and schools are lists and can be empty.\n- Data is provided as-is, as shown on the public Zillow page at the time of the lookup. It isn't official and isn't guaranteed to be accurate or complete.",
        "tags": [
          "Zillow"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/endpoints/property"
        },
        "parameters": [
          {
            "name": "zpid",
            "in": "query",
            "required": false,
            "description": "A Zillow property ID, 1–12 digits. Give exactly one of url, zpid or address.",
            "schema": {
              "type": "string"
            },
            "example": "15073305"
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "A zillow.com property link: a home (zillow.com/homedetails/..._zpid/, zillow.com/homes/..._zpid/ or a new-construction zillow.com/community/.../..._zpid/ link) or a building (zillow.com/b/... or zillow.com/apartments/...). Photo and other sub-pages after the property part are ignored. Other zillow.com pages, such as search results, return 400 bad_param, free. URL-encode it. Max 500 characters. Short links (zil.lw) and m.zillow.com links aren't accepted (400 bad_param, free): send the full www.zillow.com link.",
            "schema": {
              "type": "string"
            },
            "example": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/"
          },
          {
            "name": "address",
            "in": "query",
            "required": false,
            "description": "A one-line US address with a house number, 5-200 characters. We find the matching Zillow property first. Include the unit number for condos and apartments.",
            "schema": {
              "type": "string"
            },
            "example": "2640 Steiner St, San Francisco, CA 94115"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Optional hint for which listing to read, for a home listed for sale and for rent at once. It is a hint, not a filter: a home served from the 24-hour cache, or found first by our primary source, comes back with whatever listing it has, so check homeStatus. When the home returned doesn't have the status you asked for, meta.warning says so. If omitted, the status is detected automatically.",
            "schema": {
              "type": "string",
              "enum": [
                "for_sale",
                "sold",
                "for_rent"
              ]
            }
          },
          {
            "name": "raw",
            "in": "query",
            "required": false,
            "description": "Also return the unmapped source record as data.raw. Unstable: it is the record of whichever source served the lookup, so its keys and casing differ between lookups (even of the same home) and change without notice. Don't parse it; use the documented fields.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              },
              "x-cache": {
                "$ref": "#/components/headers/x-cache"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/x-ratelimit-limit"
              },
              "x-ratelimit-remaining": {
                "$ref": "#/components/headers/x-ratelimit-remaining"
              },
              "x-credits-remaining": {
                "$ref": "#/components/headers/x-credits-remaining"
              },
              "x-credits-limit": {
                "$ref": "#/components/headers/x-credits-limit"
              },
              "x-credits-used": {
                "$ref": "#/components/headers/x-credits-used"
              },
              "x-credits-reset": {
                "$ref": "#/components/headers/x-credits-reset"
              },
              "x-credits-prepaid-remaining": {
                "$ref": "#/components/headers/x-credits-prepaid-remaining"
              },
              "x-credits-overage-remaining": {
                "$ref": "#/components/headers/x-credits-overage-remaining"
              },
              "x-spend-limit-usd": {
                "$ref": "#/components/headers/x-spend-limit-usd"
              },
              "server-timing": {
                "$ref": "#/components/headers/server-timing"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropertyResponse"
                },
                "example": {
                  "data": {
                    "zpid": "15073305",
                    "detailUrl": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                    "address": "2640 Steiner St, San Francisco, CA 94115",
                    "streetAddress": "2640 Steiner St",
                    "unit": null,
                    "city": "San Francisco",
                    "state": "CA",
                    "zipcode": "94115",
                    "county": "San Francisco County",
                    "neighborhood": null,
                    "latitude": 37.79,
                    "longitude": -122.44,
                    "price": 525000,
                    "currency": "USD",
                    "homeStatus": "FOR_SALE",
                    "statusText": "For sale",
                    "homeType": "SINGLE_FAMILY",
                    "beds": 3,
                    "baths": 2,
                    "livingArea": 1840,
                    "lotSize": 6970,
                    "yearBuilt": 1998,
                    "pricePerSquareFoot": 285,
                    "zestimate": 512000,
                    "rentZestimate": 2650,
                    "daysOnZillow": 12,
                    "datePosted": "2026-10-07",
                    "dateSold": null,
                    "lastSoldPrice": null,
                    "monthlyHoaFee": null,
                    "taxAnnualAmount": 9840,
                    "taxAssessedValue": 498000,
                    "propertyTaxRate": 1.98,
                    "pageViewCount": null,
                    "favoriteCount": null,
                    "parcelId": null,
                    "description": "Three-bedroom home near downtown.",
                    "imgSrc": "https://photos.example.com/15073305-1.jpg",
                    "photos": [
                      "https://photos.example.com/15073305-1.jpg",
                      "https://photos.example.com/15073305-2.jpg"
                    ],
                    "photoCount": 2,
                    "brokerName": "Example Realty",
                    "brokerPhone": null,
                    "agentName": null,
                    "agentPhone": null,
                    "agentLicenseNumber": null,
                    "mlsId": null,
                    "mlsName": null,
                    "priceHistory": [
                      {
                        "date": "2026-10-07",
                        "event": "Listed for sale",
                        "price": 525000,
                        "pricePerSquareFoot": 285,
                        "priceChangeRate": null,
                        "source": "Example MLS"
                      }
                    ],
                    "taxHistory": [
                      {
                        "year": 2025,
                        "taxPaid": 9840,
                        "value": 498000,
                        "taxIncreaseRate": null,
                        "valueIncreaseRate": null
                      }
                    ],
                    "schools": [],
                    "virtualTourUrl": null,
                    "scrapedAt": "2026-10-19T14:02:11.000Z",
                    "calculated": {
                      "price_per_sqft": 285.33,
                      "zestimate_per_sqft": 278.26,
                      "rent_to_price_ratio": 0.0606,
                      "rent_to_price_basis": "price",
                      "gross_yield_pct": 6.06,
                      "price_vs_zestimate_pct": 2.54,
                      "last_sale_price": null,
                      "last_sale_date": null,
                      "change_since_last_sale_pct": null,
                      "change_since_last_sale_basis": null,
                      "days_on_market": 12,
                      "price_drops_count": 0,
                      "last_price_change": null
                    }
                  },
                  "meta": {
                    "credits": 1,
                    "cached": false,
                    "cache_key": "zpid:15073305",
                    "fetched_at": "2026-10-19T14:02:11.000Z",
                    "source": {
                      "note": "Read from the public Zillow page. Field availability varies by listing. PropertiesAPI is not affiliated with Zillow."
                    },
                    "release": "v1",
                    "took_ms": 14230,
                    "completeness": {
                      "fields": {
                        "lotSize": {
                          "availability": "supplied"
                        },
                        "propertyTaxRate": {
                          "availability": "supplied"
                        },
                        "pageViewCount": {
                          "availability": "not_returned"
                        },
                        "favoriteCount": {
                          "availability": "not_returned"
                        },
                        "photos": {
                          "availability": "supplied",
                          "count": 2
                        },
                        "priceHistory": {
                          "availability": "supplied",
                          "count": 1
                        },
                        "taxHistory": {
                          "availability": "supplied",
                          "count": 1
                        },
                        "schools": {
                          "availability": "empty_array",
                          "count": 0
                        },
                        "virtualTourUrl": {
                          "availability": "not_returned"
                        },
                        "dateSold": {
                          "availability": "not_returned"
                        }
                      },
                      "required": [],
                      "missing": [],
                      "requirements_satisfied": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/PropertyE400"
          },
          "401": {
            "$ref": "#/components/responses/PropertyE401"
          },
          "402": {
            "$ref": "#/components/responses/PropertyE402"
          },
          "404": {
            "$ref": "#/components/responses/PropertyE404"
          },
          "409": {
            "$ref": "#/components/responses/PropertyE409"
          },
          "413": {
            "$ref": "#/components/responses/PropertyE413"
          },
          "422": {
            "$ref": "#/components/responses/PropertyE422"
          },
          "429": {
            "$ref": "#/components/responses/PropertyE429"
          },
          "500": {
            "$ref": "#/components/responses/PropertyE500"
          },
          "502": {
            "$ref": "#/components/responses/PropertyE502"
          },
          "503": {
            "$ref": "#/components/responses/PropertyE503"
          },
          "504": {
            "$ref": "#/components/responses/PropertyE504"
          }
        }
      }
    },
    "/zillow/batch": {
      "post": {
        "operationId": "batchZillowProperties",
        "summary": "Zillow batch lookup",
        "description": "Up to 50 Zillow URLs or addresses in one request. Each item is looked up exactly as GET /v1/zillow/property would, and comes back with its own ok flag.\n\nBilling: 1 credit per successful live lookup, by Zillow link or address. Repeat lookups of the same home within 24 hours come from cache and are free. Errors aren't billed.\n\n- More than 50 items returns 400 too_many_items. An empty or missing items array, or a body that isn't JSON, returns 400 bad_body; a body over 64 KB returns 413 bad_body, and one that doesn't arrive in time returns 408 request_timeout. Split larger lists into several requests.\n- The request itself can fail with status 400, 401, 408, 413, 429, 500 or 503 (see Errors). Once the request is accepted, each item has its own result.\n- data has one result per item, in the same order, with its index. Check each result's ok: one failed item doesn't fail the batch. A failed item has error { code, message, details } with the same codes as GET /v1/zillow/property (such as credits_exhausted, not_found, zpid_mismatch, address_ambiguous, rate_limited, upstream_busy and upstream_timeout), plus batch_deadline and batch_response_too_large. It carries no HTTP status of its own: the error table gives each code's status.\n- When the account runs out of credits or of its per-minute limit part-way, meta.credits_exhausted or meta.rate_limited lists the indexes of the items that weren't looked up (they cost nothing). meta.credits_remaining is the included credits left, for account keys.\n- Items run concurrently within an overall time limit of about 280 seconds. Items that couldn't start in time come back with error code batch_deadline; retry those.\n- meta.credits is the number of credits charged. Only successful live lookups count; cache hits and failed items are free.\n- Each successful item's data has the same fields as GET /v1/zillow/property, data.calculated included.",
        "tags": [
          "Zillow"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/endpoints/batch"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK: the batch ran. Each item in data has its own ok flag and, when it failed, its error.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/x-ratelimit-limit"
              },
              "x-ratelimit-remaining": {
                "$ref": "#/components/headers/x-ratelimit-remaining"
              },
              "x-credits-remaining": {
                "$ref": "#/components/headers/x-credits-remaining"
              },
              "x-credits-limit": {
                "$ref": "#/components/headers/x-credits-limit"
              },
              "x-credits-used": {
                "$ref": "#/components/headers/x-credits-used"
              },
              "x-credits-reset": {
                "$ref": "#/components/headers/x-credits-reset"
              },
              "x-credits-prepaid-remaining": {
                "$ref": "#/components/headers/x-credits-prepaid-remaining"
              },
              "x-credits-overage-remaining": {
                "$ref": "#/components/headers/x-credits-overage-remaining"
              },
              "x-spend-limit-usd": {
                "$ref": "#/components/headers/x-spend-limit-usd"
              },
              "server-timing": {
                "$ref": "#/components/headers/server-timing"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                },
                "example": {
                  "data": [
                    {
                      "index": 0,
                      "ok": true,
                      "data": {
                        "zpid": "15073305",
                        "detailUrl": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                        "address": "2640 Steiner St, San Francisco, CA 94115",
                        "streetAddress": "2640 Steiner St",
                        "unit": null,
                        "city": "San Francisco",
                        "state": "CA",
                        "zipcode": "94115",
                        "county": "San Francisco County",
                        "neighborhood": null,
                        "latitude": 37.79,
                        "longitude": -122.44,
                        "price": 525000,
                        "currency": "USD",
                        "homeStatus": "FOR_SALE",
                        "statusText": "For sale",
                        "homeType": "SINGLE_FAMILY",
                        "beds": 3,
                        "baths": 2,
                        "livingArea": 1840,
                        "lotSize": 6970,
                        "yearBuilt": 1998,
                        "pricePerSquareFoot": 285,
                        "zestimate": 512000,
                        "rentZestimate": 2650,
                        "daysOnZillow": 12,
                        "datePosted": "2026-10-07",
                        "dateSold": null,
                        "lastSoldPrice": null,
                        "monthlyHoaFee": null,
                        "taxAnnualAmount": 9840,
                        "taxAssessedValue": 498000,
                        "propertyTaxRate": 1.98,
                        "pageViewCount": null,
                        "favoriteCount": null,
                        "parcelId": null,
                        "description": "Three-bedroom home near downtown.",
                        "imgSrc": "https://photos.example.com/15073305-1.jpg",
                        "photos": [
                          "https://photos.example.com/15073305-1.jpg",
                          "https://photos.example.com/15073305-2.jpg"
                        ],
                        "photoCount": 2,
                        "brokerName": "Example Realty",
                        "brokerPhone": null,
                        "agentName": null,
                        "agentPhone": null,
                        "agentLicenseNumber": null,
                        "mlsId": null,
                        "mlsName": null,
                        "priceHistory": [
                          {
                            "date": "2026-10-07",
                            "event": "Listed for sale",
                            "price": 525000,
                            "pricePerSquareFoot": 285,
                            "priceChangeRate": null,
                            "source": "Example MLS"
                          }
                        ],
                        "taxHistory": [
                          {
                            "year": 2025,
                            "taxPaid": 9840,
                            "value": 498000,
                            "taxIncreaseRate": null,
                            "valueIncreaseRate": null
                          }
                        ],
                        "schools": [],
                        "virtualTourUrl": null,
                        "scrapedAt": "2026-10-19T14:02:11.000Z",
                        "calculated": {
                          "price_per_sqft": 285.33,
                          "zestimate_per_sqft": 278.26,
                          "rent_to_price_ratio": 0.0606,
                          "rent_to_price_basis": "price",
                          "gross_yield_pct": 6.06,
                          "price_vs_zestimate_pct": 2.54,
                          "last_sale_price": null,
                          "last_sale_date": null,
                          "change_since_last_sale_pct": null,
                          "change_since_last_sale_basis": null,
                          "days_on_market": 12,
                          "price_drops_count": 0,
                          "last_price_change": null
                        }
                      },
                      "meta": {
                        "credits": 0,
                        "cached": true,
                        "cache_key": "zpid:15073305",
                        "fetched_at": "2026-10-19T14:02:11.000Z",
                        "source": {
                          "note": "Read from the public Zillow page. Field availability varies by listing. PropertiesAPI is not affiliated with Zillow."
                        },
                        "release": "v1",
                        "took_ms": 2,
                        "completeness": {
                          "fields": {
                            "lotSize": {
                              "availability": "supplied"
                            },
                            "propertyTaxRate": {
                              "availability": "supplied"
                            },
                            "pageViewCount": {
                              "availability": "not_returned"
                            },
                            "favoriteCount": {
                              "availability": "not_returned"
                            },
                            "photos": {
                              "availability": "supplied",
                              "count": 2
                            },
                            "priceHistory": {
                              "availability": "supplied",
                              "count": 1
                            },
                            "taxHistory": {
                              "availability": "supplied",
                              "count": 1
                            },
                            "schools": {
                              "availability": "empty_array",
                              "count": 0
                            },
                            "virtualTourUrl": {
                              "availability": "not_returned"
                            },
                            "dateSold": {
                              "availability": "not_returned"
                            }
                          },
                          "required": [],
                          "missing": [],
                          "requirements_satisfied": true
                        }
                      }
                    },
                    {
                      "index": 1,
                      "ok": false,
                      "error": {
                        "code": "not_found",
                        "message": "No Zillow property matches this input"
                      }
                    }
                  ],
                  "meta": {
                    "count": 2,
                    "succeeded": 1,
                    "failed": 1,
                    "cache_hits": 1,
                    "credits": 0,
                    "release": "v1",
                    "took_ms": 9120
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BatchE400"
          },
          "401": {
            "$ref": "#/components/responses/BatchE401"
          },
          "408": {
            "$ref": "#/components/responses/BatchE408"
          },
          "413": {
            "$ref": "#/components/responses/BatchE413"
          },
          "429": {
            "$ref": "#/components/responses/BatchE429"
          },
          "500": {
            "$ref": "#/components/responses/BatchE500"
          },
          "503": {
            "$ref": "#/components/responses/BatchE503"
          }
        },
        "requestBody": {
          "required": true,
          "description": "items: 1 to 50 entries. Each is a string (a zillow.com link, an address, or a zpid of 1–12 digits) or an object with exactly one of url, address or zpid. Optional status (for_sale, sold or for_rent) applies to every item; optional raw adds data.raw only when it is the JSON boolean true (any other value, such as the string \"true\", is ignored).",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "items": [
                  "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                  "1 Nowhere Rd, San Francisco, CA 94115",
                  {
                    "address": "2640 Steiner St, San Francisco, CA 94115"
                  },
                  {
                    "zpid": "2064142765"
                  }
                ],
                "status": "for_sale"
              }
            }
          }
        }
      }
    },
    "/zillow/rental-analysis": {
      "get": {
        "operationId": "getRentalAnalysis",
        "summary": "Zillow rental analysis",
        "description": "A rental property analysis for one home by Zillow link, zpid or address: cash flow, cap rate, cash-on-cash return, DSCR, GRM, the 1% rule, break-even rent and a five-year projection. Price, rent, taxes and HOA are filled from the property; every other input is a documented default you can override.\n\nBilling: 1 credit per successful live lookup of the home, the same as GET /v1/zillow/property; a home looked up within 24 hours comes from cache and is free. The analysis adds no charge. Errors before or during the lookup aren't billed.\n\n- Send exactly one of url, zpid or address, as JSON (POST) or as query parameters (GET). Every other field is an optional override, used as you pass it (source request).\n- The lookup is GET /v1/zillow/property's own: the same key, rate limits, credits and 24-hour cache. Your dashboard lists these calls as property lookups.\n- price and monthly_rent are never invented. When the home has no list price, sold price or Zestimate, or no rent Zestimate (or listed rent), and you didn't pass one, the answer is 422 rental_input_missing: details.missing names the input, and nothing else about the home is returned. The record is refused before it is cached or billed, so the 422 costs nothing; ask again with the input and it is a normal lookup (1 credit live, none from the cache).\n- interest_rate_pct defaults to an assumed rate, not a quoted or current mortgage rate. annual_insurance, and annual_property_tax when the home has no tax figure, are estimates as a share of the price. data.warnings lists every such input; pass your own numbers.\n- Override values are checked before the lookup: a bad value returns 400 bad_param (details.fields names it), and nothing is looked up or billed.\n- Money is in USD rounded to cents; percentages and ratios to 2 decimals. The formulas are listed under Response fields.\n- The analysis is plain arithmetic on the inputs, for illustration. It isn't financial advice, an appraisal or a loan quote (data.disclaimer). The same engine runs the free rental property calculator.",
        "tags": [
          "Zillow"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/endpoints/rental-analysis"
        },
        "parameters": [
          {
            "name": "zpid",
            "in": "query",
            "required": false,
            "description": "A Zillow property ID, 1–12 digits. Give exactly one of url, zpid or address.",
            "schema": {
              "type": "string"
            },
            "example": "15073305"
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "A zillow.com property link: a home (zillow.com/homedetails/..._zpid/, zillow.com/homes/..._zpid/ or a new-construction zillow.com/community/.../..._zpid/ link) or a building (zillow.com/b/... or zillow.com/apartments/...). Photo and other sub-pages after the property part are ignored. Other zillow.com pages, such as search results, return 400 bad_param, free. URL-encode it. Max 500 characters. Short links (zil.lw) and m.zillow.com links aren't accepted (400 bad_param, free): send the full www.zillow.com link.",
            "schema": {
              "type": "string"
            },
            "example": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/"
          },
          {
            "name": "address",
            "in": "query",
            "required": false,
            "description": "A one-line US address with a house number, 5-200 characters. We find the matching Zillow property first. Include the unit number for condos and apartments.",
            "schema": {
              "type": "string"
            },
            "example": "2640 Steiner St, San Francisco, CA 94115"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Optional hint for which listing to read, for a home listed for sale and for rent at once. It is a hint, not a filter: a home served from the 24-hour cache, or found first by our primary source, comes back with whatever listing it has, so check homeStatus. When the home returned doesn't have the status you asked for, meta.warning says so. If omitted, the status is detected automatically.",
            "schema": {
              "type": "string",
              "enum": [
                "for_sale",
                "sold",
                "for_rent"
              ]
            }
          },
          {
            "name": "price",
            "in": "query",
            "required": false,
            "description": "Purchase price, USD. Required. Auto-filled from the list or sold price, else Zillow's Zestimate.",
            "schema": {
              "type": "number",
              "maximum": 1000000000,
              "exclusiveMinimum": 0
            }
          },
          {
            "name": "down_payment_pct",
            "in": "query",
            "required": false,
            "description": "Down payment, percent of the price. Default 20% (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 20,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "interest_rate_pct",
            "in": "query",
            "required": false,
            "description": "Interest rate, percent a year. Default 7% a year: assumed; edit it. Not a quoted rate.",
            "schema": {
              "type": "number",
              "default": 7,
              "maximum": 30,
              "minimum": 0
            }
          },
          {
            "name": "loan_term_years",
            "in": "query",
            "required": false,
            "description": "Loan term, years. Default 30 years (an assumption; edit it).",
            "schema": {
              "type": "integer",
              "default": 30,
              "maximum": 50,
              "minimum": 1
            }
          },
          {
            "name": "closing_costs_pct",
            "in": "query",
            "required": false,
            "description": "Closing costs, percent of the price. Default 3% of the price (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 3,
              "maximum": 20,
              "minimum": 0
            }
          },
          {
            "name": "monthly_rent",
            "in": "query",
            "required": false,
            "description": "Monthly rent, USD. Required. Auto-filled from Zillow's rent Zestimate (an estimate).",
            "schema": {
              "type": "number",
              "maximum": 10000000,
              "minimum": 0
            }
          },
          {
            "name": "vacancy_pct",
            "in": "query",
            "required": false,
            "description": "Vacancy, percent. Default 5% of gross rent (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 5,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "management_pct",
            "in": "query",
            "required": false,
            "description": "Property management, percent. Default 8% of rent collected (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 8,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "maintenance_pct",
            "in": "query",
            "required": false,
            "description": "Maintenance, percent. Default 5% of gross rent (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 5,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "capex_pct",
            "in": "query",
            "required": false,
            "description": "Capital expenditures, percent. Default 5% of gross rent, set aside for big repairs (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 5,
              "maximum": 100,
              "minimum": 0
            }
          },
          {
            "name": "annual_property_tax",
            "in": "query",
            "required": false,
            "description": "Property tax a year, USD. Auto-filled from the tax history when there is one; otherwise an estimate of 1% of the price a year (edit it).",
            "schema": {
              "type": "number",
              "maximum": 100000000,
              "minimum": 0
            }
          },
          {
            "name": "annual_insurance",
            "in": "query",
            "required": false,
            "description": "Insurance a year, USD. An estimate of 0.5% of the price a year, not a quote (edit it).",
            "schema": {
              "type": "number",
              "maximum": 100000000,
              "minimum": 0
            }
          },
          {
            "name": "monthly_hoa",
            "in": "query",
            "required": false,
            "description": "HOA a month, USD. Auto-filled from the listing's HOA fee; default 0.",
            "schema": {
              "type": "number",
              "default": 0,
              "maximum": 10000000,
              "minimum": 0
            }
          },
          {
            "name": "other_monthly_expenses",
            "in": "query",
            "required": false,
            "description": "Other costs a month, USD. Utilities you pay, landscaping and so on; default 0.",
            "schema": {
              "type": "number",
              "default": 0,
              "maximum": 10000000,
              "minimum": 0
            }
          },
          {
            "name": "rent_growth_pct",
            "in": "query",
            "required": false,
            "description": "Rent growth, percent a year. Projection only. Default 3% a year (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 3,
              "maximum": 50,
              "minimum": -20
            }
          },
          {
            "name": "appreciation_pct",
            "in": "query",
            "required": false,
            "description": "Appreciation, percent a year. Projection only. Default 3% a year (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 3,
              "maximum": 50,
              "minimum": -20
            }
          },
          {
            "name": "expense_growth_pct",
            "in": "query",
            "required": false,
            "description": "Expense growth, percent a year. Projection only: taxes, insurance, HOA and other costs. Default 3% a year (an assumption; edit it).",
            "schema": {
              "type": "number",
              "default": 3,
              "maximum": 50,
              "minimum": -20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK: the analysis.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              },
              "x-cache": {
                "$ref": "#/components/headers/x-cache"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/x-ratelimit-limit"
              },
              "x-ratelimit-remaining": {
                "$ref": "#/components/headers/x-ratelimit-remaining"
              },
              "x-credits-remaining": {
                "$ref": "#/components/headers/x-credits-remaining"
              },
              "x-credits-limit": {
                "$ref": "#/components/headers/x-credits-limit"
              },
              "x-credits-used": {
                "$ref": "#/components/headers/x-credits-used"
              },
              "x-credits-reset": {
                "$ref": "#/components/headers/x-credits-reset"
              },
              "x-credits-prepaid-remaining": {
                "$ref": "#/components/headers/x-credits-prepaid-remaining"
              },
              "x-credits-overage-remaining": {
                "$ref": "#/components/headers/x-credits-overage-remaining"
              },
              "x-spend-limit-usd": {
                "$ref": "#/components/headers/x-spend-limit-usd"
              },
              "server-timing": {
                "$ref": "#/components/headers/server-timing"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RentalAnalysisResponse"
                },
                "example": {
                  "data": {
                    "property": {
                      "zpid": "15073305",
                      "address": "2640 Steiner St, San Francisco, CA 94115",
                      "detailUrl": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                      "homeStatus": "FOR_SALE",
                      "homeType": "SINGLE_FAMILY",
                      "beds": 3,
                      "baths": 2,
                      "livingArea": 1840,
                      "yearBuilt": 1998,
                      "price": 525000,
                      "zestimate": 512000,
                      "rentZestimate": 2650,
                      "monthlyHoaFee": null,
                      "taxAnnualAmount": 9840,
                      "taxAssessedValue": 498000,
                      "propertyTaxRate": 1.98
                    },
                    "inputs": {
                      "price": {
                        "value": 525000,
                        "source": "property",
                        "basis": "list price"
                      },
                      "down_payment_pct": {
                        "value": 20,
                        "source": "default",
                        "note": "Default 20% (an assumption; edit it)."
                      },
                      "interest_rate_pct": {
                        "value": 6.5,
                        "source": "request"
                      },
                      "loan_term_years": {
                        "value": 30,
                        "source": "default",
                        "note": "Default 30 years (an assumption; edit it)."
                      },
                      "closing_costs_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Default 3% of the price (an assumption; edit it)."
                      },
                      "monthly_rent": {
                        "value": 2650,
                        "source": "property",
                        "basis": "rent Zestimate"
                      },
                      "vacancy_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent (an assumption; edit it)."
                      },
                      "management_pct": {
                        "value": 8,
                        "source": "default",
                        "note": "Default 8% of rent collected (an assumption; edit it)."
                      },
                      "maintenance_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent (an assumption; edit it)."
                      },
                      "capex_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent, set aside for big repairs (an assumption; edit it)."
                      },
                      "annual_property_tax": {
                        "value": 9840,
                        "source": "property",
                        "basis": "tax history (2025)"
                      },
                      "annual_insurance": {
                        "value": 2625,
                        "source": "default",
                        "note": "An estimate of 0.5% of the price a year, not a quote (edit it)."
                      },
                      "monthly_hoa": {
                        "value": 0,
                        "source": "default",
                        "note": "Auto-filled from the listing's HOA fee; default 0."
                      },
                      "other_monthly_expenses": {
                        "value": 0,
                        "source": "default",
                        "note": "Utilities you pay, landscaping and so on; default 0."
                      },
                      "rent_growth_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only. Default 3% a year (an assumption; edit it)."
                      },
                      "appreciation_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only. Default 3% a year (an assumption; edit it)."
                      },
                      "expense_growth_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only: taxes, insurance, HOA and other costs. Default 3% a year (an assumption; edit it)."
                      }
                    },
                    "metrics": {
                      "down_payment": 105000,
                      "loan_amount": 420000,
                      "closing_costs": 15750,
                      "total_cash_needed": 120750,
                      "monthly_principal_and_interest": 2654.69,
                      "annual_debt_service": 31856.23,
                      "gross_monthly_rent": 2650,
                      "gross_annual_income": 31800,
                      "vacancy_loss": 1590,
                      "effective_gross_income": 30210,
                      "operating_expenses": {
                        "management": 2416.8,
                        "maintenance": 1590,
                        "capex": 1590,
                        "property_tax": 9840,
                        "insurance": 2625,
                        "hoa": 0,
                        "other": 0
                      },
                      "total_operating_expenses": 18061.8,
                      "noi": 12148.2,
                      "annual_cash_flow": -19708.03,
                      "monthly_cash_flow": -1642.34,
                      "cap_rate_pct": 2.31,
                      "cash_on_cash_pct": -16.32,
                      "dscr": 0.38,
                      "grm": 16.51,
                      "one_percent_rule": {
                        "passes": false,
                        "rent_to_price_pct": 0.5
                      },
                      "break_even_rent": 4771.88
                    },
                    "projection": [
                      {
                        "year": 1,
                        "monthly_rent": 2650,
                        "noi": 12148.2,
                        "annual_cash_flow": -19708.03,
                        "cumulative_cash_flow": -19708.03,
                        "property_value": 540750,
                        "loan_balance": 415305.55,
                        "equity": 125444.45,
                        "total_profit": -15013.58,
                        "total_return_pct": -12.43
                      },
                      {
                        "year": 2,
                        "monthly_rent": 2729.5,
                        "noi": 12512.65,
                        "annual_cash_flow": -19343.58,
                        "cumulative_cash_flow": -39051.61,
                        "property_value": 556972.5,
                        "loan_balance": 410296.71,
                        "equity": 146675.79,
                        "total_profit": -13125.82,
                        "total_return_pct": -10.87
                      },
                      {
                        "year": 3,
                        "monthly_rent": 2811.39,
                        "noi": 12888.03,
                        "annual_cash_flow": -18968.2,
                        "cumulative_cash_flow": -58019.81,
                        "property_value": 573681.68,
                        "loan_balance": 404952.42,
                        "equity": 168729.26,
                        "total_profit": -10040.55,
                        "total_return_pct": -8.32
                      },
                      {
                        "year": 4,
                        "monthly_rent": 2895.73,
                        "noi": 13274.67,
                        "annual_cash_flow": -18581.56,
                        "cumulative_cash_flow": -76601.38,
                        "property_value": 590892.13,
                        "loan_balance": 399250.2,
                        "equity": 191641.92,
                        "total_profit": -5709.46,
                        "total_return_pct": -4.73
                      },
                      {
                        "year": 5,
                        "monthly_rent": 2982.6,
                        "noi": 13672.91,
                        "annual_cash_flow": -18183.32,
                        "cumulative_cash_flow": -94784.7,
                        "property_value": 608618.89,
                        "loan_balance": 393166.11,
                        "equity": 215452.78,
                        "total_profit": -81.91,
                        "total_return_pct": -0.07
                      }
                    ],
                    "warnings": [
                      "monthly_rent is Zillow's rent Zestimate, an estimate. Pass monthly_rent if you know the market rent.",
                      "annual_insurance is an estimate (0.5% of the price a year), not a quote. Pass a real quote.",
                      "Cash flow is negative with these inputs: the rent doesn't cover the costs and the loan payment."
                    ],
                    "disclaimer": "Not financial advice. These figures are estimates for illustration: plain arithmetic on the inputs shown, several of which are assumptions or estimates (see inputs). The Zestimate and rent Zestimate are Zillow's estimates, not appraisals or offers. Check every input with a lender, an insurer and a local expert before you rely on it."
                  },
                  "meta": {
                    "credits": 1,
                    "cached": false,
                    "cache_key": "zpid:15073305",
                    "fetched_at": "2026-10-19T14:02:11.000Z",
                    "source": {
                      "note": "Read from the public Zillow page. Field availability varies by listing. PropertiesAPI is not affiliated with Zillow."
                    },
                    "release": "v1",
                    "took_ms": 14236
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RentalE400"
          },
          "401": {
            "$ref": "#/components/responses/RentalE401"
          },
          "402": {
            "$ref": "#/components/responses/RentalE402"
          },
          "404": {
            "$ref": "#/components/responses/RentalE404"
          },
          "409": {
            "$ref": "#/components/responses/RentalE409"
          },
          "422": {
            "$ref": "#/components/responses/RentalE422"
          },
          "429": {
            "$ref": "#/components/responses/RentalE429"
          },
          "500": {
            "$ref": "#/components/responses/RentalE500"
          },
          "502": {
            "$ref": "#/components/responses/RentalE502"
          },
          "503": {
            "$ref": "#/components/responses/RentalE503"
          },
          "504": {
            "$ref": "#/components/responses/RentalE504"
          }
        }
      },
      "post": {
        "operationId": "rentalAnalysis",
        "summary": "Zillow rental analysis",
        "description": "A rental property analysis for one home by Zillow link, zpid or address: cash flow, cap rate, cash-on-cash return, DSCR, GRM, the 1% rule, break-even rent and a five-year projection. Price, rent, taxes and HOA are filled from the property; every other input is a documented default you can override.\n\nBilling: 1 credit per successful live lookup of the home, the same as GET /v1/zillow/property; a home looked up within 24 hours comes from cache and is free. The analysis adds no charge. Errors before or during the lookup aren't billed.\n\n- Send exactly one of url, zpid or address, as JSON (POST) or as query parameters (GET). Every other field is an optional override, used as you pass it (source request).\n- The lookup is GET /v1/zillow/property's own: the same key, rate limits, credits and 24-hour cache. Your dashboard lists these calls as property lookups.\n- price and monthly_rent are never invented. When the home has no list price, sold price or Zestimate, or no rent Zestimate (or listed rent), and you didn't pass one, the answer is 422 rental_input_missing: details.missing names the input, and nothing else about the home is returned. The record is refused before it is cached or billed, so the 422 costs nothing; ask again with the input and it is a normal lookup (1 credit live, none from the cache).\n- interest_rate_pct defaults to an assumed rate, not a quoted or current mortgage rate. annual_insurance, and annual_property_tax when the home has no tax figure, are estimates as a share of the price. data.warnings lists every such input; pass your own numbers.\n- Override values are checked before the lookup: a bad value returns 400 bad_param (details.fields names it), and nothing is looked up or billed.\n- Money is in USD rounded to cents; percentages and ratios to 2 decimals. The formulas are listed under Response fields.\n- The analysis is plain arithmetic on the inputs, for illustration. It isn't financial advice, an appraisal or a loan quote (data.disclaimer). The same engine runs the free rental property calculator.",
        "tags": [
          "Zillow"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/endpoints/rental-analysis"
        },
        "parameters": [],
        "requestBody": {
          "required": true,
          "description": "Exactly one of url, zpid or address, optional status, and any of the inputs above as JSON numbers (or numeric strings). GET takes the same names as query parameters. Inputs you leave out are filled from the property or from the documented defaults.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RentalAnalysisRequest"
              },
              "example": {
                "url": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                "interest_rate_pct": 6.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK: the analysis.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              },
              "x-cache": {
                "$ref": "#/components/headers/x-cache"
              },
              "x-ratelimit-limit": {
                "$ref": "#/components/headers/x-ratelimit-limit"
              },
              "x-ratelimit-remaining": {
                "$ref": "#/components/headers/x-ratelimit-remaining"
              },
              "x-credits-remaining": {
                "$ref": "#/components/headers/x-credits-remaining"
              },
              "x-credits-limit": {
                "$ref": "#/components/headers/x-credits-limit"
              },
              "x-credits-used": {
                "$ref": "#/components/headers/x-credits-used"
              },
              "x-credits-reset": {
                "$ref": "#/components/headers/x-credits-reset"
              },
              "x-credits-prepaid-remaining": {
                "$ref": "#/components/headers/x-credits-prepaid-remaining"
              },
              "x-credits-overage-remaining": {
                "$ref": "#/components/headers/x-credits-overage-remaining"
              },
              "x-spend-limit-usd": {
                "$ref": "#/components/headers/x-spend-limit-usd"
              },
              "server-timing": {
                "$ref": "#/components/headers/server-timing"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RentalAnalysisResponse"
                },
                "example": {
                  "data": {
                    "property": {
                      "zpid": "15073305",
                      "address": "2640 Steiner St, San Francisco, CA 94115",
                      "detailUrl": "https://www.zillow.com/homedetails/2640-Steiner-St-San-Francisco-CA-94115/15073305_zpid/",
                      "homeStatus": "FOR_SALE",
                      "homeType": "SINGLE_FAMILY",
                      "beds": 3,
                      "baths": 2,
                      "livingArea": 1840,
                      "yearBuilt": 1998,
                      "price": 525000,
                      "zestimate": 512000,
                      "rentZestimate": 2650,
                      "monthlyHoaFee": null,
                      "taxAnnualAmount": 9840,
                      "taxAssessedValue": 498000,
                      "propertyTaxRate": 1.98
                    },
                    "inputs": {
                      "price": {
                        "value": 525000,
                        "source": "property",
                        "basis": "list price"
                      },
                      "down_payment_pct": {
                        "value": 20,
                        "source": "default",
                        "note": "Default 20% (an assumption; edit it)."
                      },
                      "interest_rate_pct": {
                        "value": 6.5,
                        "source": "request"
                      },
                      "loan_term_years": {
                        "value": 30,
                        "source": "default",
                        "note": "Default 30 years (an assumption; edit it)."
                      },
                      "closing_costs_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Default 3% of the price (an assumption; edit it)."
                      },
                      "monthly_rent": {
                        "value": 2650,
                        "source": "property",
                        "basis": "rent Zestimate"
                      },
                      "vacancy_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent (an assumption; edit it)."
                      },
                      "management_pct": {
                        "value": 8,
                        "source": "default",
                        "note": "Default 8% of rent collected (an assumption; edit it)."
                      },
                      "maintenance_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent (an assumption; edit it)."
                      },
                      "capex_pct": {
                        "value": 5,
                        "source": "default",
                        "note": "Default 5% of gross rent, set aside for big repairs (an assumption; edit it)."
                      },
                      "annual_property_tax": {
                        "value": 9840,
                        "source": "property",
                        "basis": "tax history (2025)"
                      },
                      "annual_insurance": {
                        "value": 2625,
                        "source": "default",
                        "note": "An estimate of 0.5% of the price a year, not a quote (edit it)."
                      },
                      "monthly_hoa": {
                        "value": 0,
                        "source": "default",
                        "note": "Auto-filled from the listing's HOA fee; default 0."
                      },
                      "other_monthly_expenses": {
                        "value": 0,
                        "source": "default",
                        "note": "Utilities you pay, landscaping and so on; default 0."
                      },
                      "rent_growth_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only. Default 3% a year (an assumption; edit it)."
                      },
                      "appreciation_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only. Default 3% a year (an assumption; edit it)."
                      },
                      "expense_growth_pct": {
                        "value": 3,
                        "source": "default",
                        "note": "Projection only: taxes, insurance, HOA and other costs. Default 3% a year (an assumption; edit it)."
                      }
                    },
                    "metrics": {
                      "down_payment": 105000,
                      "loan_amount": 420000,
                      "closing_costs": 15750,
                      "total_cash_needed": 120750,
                      "monthly_principal_and_interest": 2654.69,
                      "annual_debt_service": 31856.23,
                      "gross_monthly_rent": 2650,
                      "gross_annual_income": 31800,
                      "vacancy_loss": 1590,
                      "effective_gross_income": 30210,
                      "operating_expenses": {
                        "management": 2416.8,
                        "maintenance": 1590,
                        "capex": 1590,
                        "property_tax": 9840,
                        "insurance": 2625,
                        "hoa": 0,
                        "other": 0
                      },
                      "total_operating_expenses": 18061.8,
                      "noi": 12148.2,
                      "annual_cash_flow": -19708.03,
                      "monthly_cash_flow": -1642.34,
                      "cap_rate_pct": 2.31,
                      "cash_on_cash_pct": -16.32,
                      "dscr": 0.38,
                      "grm": 16.51,
                      "one_percent_rule": {
                        "passes": false,
                        "rent_to_price_pct": 0.5
                      },
                      "break_even_rent": 4771.88
                    },
                    "projection": [
                      {
                        "year": 1,
                        "monthly_rent": 2650,
                        "noi": 12148.2,
                        "annual_cash_flow": -19708.03,
                        "cumulative_cash_flow": -19708.03,
                        "property_value": 540750,
                        "loan_balance": 415305.55,
                        "equity": 125444.45,
                        "total_profit": -15013.58,
                        "total_return_pct": -12.43
                      },
                      {
                        "year": 2,
                        "monthly_rent": 2729.5,
                        "noi": 12512.65,
                        "annual_cash_flow": -19343.58,
                        "cumulative_cash_flow": -39051.61,
                        "property_value": 556972.5,
                        "loan_balance": 410296.71,
                        "equity": 146675.79,
                        "total_profit": -13125.82,
                        "total_return_pct": -10.87
                      },
                      {
                        "year": 3,
                        "monthly_rent": 2811.39,
                        "noi": 12888.03,
                        "annual_cash_flow": -18968.2,
                        "cumulative_cash_flow": -58019.81,
                        "property_value": 573681.68,
                        "loan_balance": 404952.42,
                        "equity": 168729.26,
                        "total_profit": -10040.55,
                        "total_return_pct": -8.32
                      },
                      {
                        "year": 4,
                        "monthly_rent": 2895.73,
                        "noi": 13274.67,
                        "annual_cash_flow": -18581.56,
                        "cumulative_cash_flow": -76601.38,
                        "property_value": 590892.13,
                        "loan_balance": 399250.2,
                        "equity": 191641.92,
                        "total_profit": -5709.46,
                        "total_return_pct": -4.73
                      },
                      {
                        "year": 5,
                        "monthly_rent": 2982.6,
                        "noi": 13672.91,
                        "annual_cash_flow": -18183.32,
                        "cumulative_cash_flow": -94784.7,
                        "property_value": 608618.89,
                        "loan_balance": 393166.11,
                        "equity": 215452.78,
                        "total_profit": -81.91,
                        "total_return_pct": -0.07
                      }
                    ],
                    "warnings": [
                      "monthly_rent is Zillow's rent Zestimate, an estimate. Pass monthly_rent if you know the market rent.",
                      "annual_insurance is an estimate (0.5% of the price a year), not a quote. Pass a real quote.",
                      "Cash flow is negative with these inputs: the rent doesn't cover the costs and the loan payment."
                    ],
                    "disclaimer": "Not financial advice. These figures are estimates for illustration: plain arithmetic on the inputs shown, several of which are assumptions or estimates (see inputs). The Zestimate and rent Zestimate are Zillow's estimates, not appraisals or offers. Check every input with a lender, an insurer and a local expert before you rely on it."
                  },
                  "meta": {
                    "credits": 1,
                    "cached": false,
                    "cache_key": "zpid:15073305",
                    "fetched_at": "2026-10-19T14:02:11.000Z",
                    "source": {
                      "note": "Read from the public Zillow page. Field availability varies by listing. PropertiesAPI is not affiliated with Zillow."
                    },
                    "release": "v1",
                    "took_ms": 14236
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/RentalE400"
          },
          "401": {
            "$ref": "#/components/responses/RentalE401"
          },
          "402": {
            "$ref": "#/components/responses/RentalE402"
          },
          "404": {
            "$ref": "#/components/responses/RentalE404"
          },
          "408": {
            "$ref": "#/components/responses/RentalE408"
          },
          "409": {
            "$ref": "#/components/responses/RentalE409"
          },
          "413": {
            "$ref": "#/components/responses/RentalE413"
          },
          "422": {
            "$ref": "#/components/responses/RentalE422"
          },
          "429": {
            "$ref": "#/components/responses/RentalE429"
          },
          "500": {
            "$ref": "#/components/responses/RentalE500"
          },
          "502": {
            "$ref": "#/components/responses/RentalE502"
          },
          "503": {
            "$ref": "#/components/responses/RentalE503"
          },
          "504": {
            "$ref": "#/components/responses/RentalE504"
          }
        }
      }
    },
    "/watchlists": {
      "get": {
        "operationId": "getWatchlists",
        "summary": "Your watchlists, each with its home_count.",
        "description": "Your watchlists, each with its home_count.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      },
      "post": {
        "operationId": "postWatchlists",
        "summary": "Create a watchlist.",
        "description": "Create a watchlist.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "201": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "name": "Austin flips",
                "price_change_pct": 0,
                "zestimate_change_pct": 1,
                "rent_change_pct": 0,
                "events": [
                  "price_change",
                  "status_change",
                  "zestimate_change",
                  "new_photos",
                  "rent_change"
                ]
              }
            }
          }
        }
      }
    },
    "/watchlists/{id}": {
      "get": {
        "operationId": "getWatchlistsBy_id",
        "summary": "One watchlist with its homes: each home's zpid, address, last snapshot, last check and result.",
        "description": "One watchlist with its homes: each home's zpid, address, last snapshot, last check and result.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      },
      "patch": {
        "operationId": "patchWatchlistsBy_id",
        "summary": "Change its name, thresholds or events, or pause it with { \"paused\": true }.",
        "description": "Change its name, thresholds or events, or pause it with { \"paused\": true }.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "price_change_pct": 2,
                "paused": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWatchlistsBy_id",
        "summary": "Delete the watchlist and stop watching its homes.",
        "description": "Delete the watchlist and stop watching its homes.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/watchlists/{id}/homes": {
      "post": {
        "operationId": "postWatchlistsBy_idHomes",
        "summary": "Watch a home by url, zpid or address. It is looked up now (a normal lookup: 1 credit, free from the cache) to find the home and record its first snapshot. Or send { \"items\": [...] } with up to 25 links, zpids or addresses. An apartment building (a zillow.com/apartments/ link) is watched by its link: its zpid is null. If an address turns out to be a home the list already watches, the answer is 200 with meta.already_watched and that watch.",
        "description": "Watch a home by url, zpid or address. It is looked up now (a normal lookup: 1 credit, free from the cache) to find the home and record its first snapshot. Or send { \"items\": [...] } with up to 25 links, zpids or addresses. An apartment building (a zillow.com/apartments/ link) is watched by its link: its zpid is null. If an address turns out to be a home the list already watches, the answer is 200 with meta.already_watched and that watch.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "url": "https://www.zillow.com/homedetails/.../12345_zpid/"
              }
            }
          }
        }
      }
    },
    "/watchlists/{id}/homes/{homeId}": {
      "patch": {
        "operationId": "patchWatchlistsBy_idHomesBy_homeId",
        "summary": "Pause or resume one home with { \"paused\": true | false }.",
        "description": "Pause or resume one home with { \"paused\": true | false }.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "homeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "paused": true
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "deleteWatchlistsBy_idHomesBy_homeId",
        "summary": "Stop watching a home.",
        "description": "Stop watching a home.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "homeId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/watchlists/events": {
      "get": {
        "operationId": "getWatchlistsEvents",
        "summary": "Recent events (newest first), ?limit= up to 500.",
        "description": "Recent events (newest first), ?limit= up to 500.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/watchlists/settings": {
      "get": {
        "operationId": "getWatchlistsSettings",
        "summary": "Your watched-home count and limit, whether checks are paused and why, the webhook URL and the email digest setting.",
        "description": "Your watched-home count and limit, whether checks are paused and why, the webhook URL and the email digest setting.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      },
      "patch": {
        "operationId": "patchWatchlistsSettings",
        "summary": "Set the webhook URL (https only, or null to remove it) and turn the daily email digest on or off.",
        "description": "Set the webhook URL (https only, or null to remove it) and turn the daily email digest on or off.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              },
              "example": {
                "webhook_url": "https://example.com/hooks/propertiesapi",
                "email_digest": true
              }
            }
          }
        }
      }
    },
    "/watchlists/settings/secret": {
      "post": {
        "operationId": "postWatchlistsSettingsSecret",
        "summary": "Create or rotate the webhook signing secret. It is shown in this response only.",
        "description": "Create or rotate the webhook signing secret. It is shown in this response only.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "201": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/watchlists/deliveries": {
      "get": {
        "operationId": "getWatchlistsDeliveries",
        "summary": "The webhook delivery log: status, attempts, last HTTP status and error code for each event sent.",
        "description": "The webhook delivery log: status, attempts, last HTTP status and error code for each event sent.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    },
    "/watchlists/resume": {
      "post": {
        "operationId": "postWatchlistsResume",
        "summary": "Resume checks that paused because your lookups or your spending limit ran out.",
        "description": "Resume checks that paused because your lookups or your spending limit ran out.\n\nWatchlists (W18): watch homes and get events when the price, status, Zestimate, photos or rent Zestimate change. Each daily check of a watched home is a lookup: 1 credit, or free when the home comes from the 24-hour cache. Checks use your lookups like any other call (included, then prepaid credits, then usage past your plan) and pause when they run out. Watched homes per plan: Free 5, Starter 50, Pro 200, Business 1,000, Scale 3,000.\n\n- Watched homes are checked about once a day. There is no guaranteed time of day.\n- Each check is a lookup through the same gate as the API: 1 credit when it runs live, free when the home comes from the 24-hour cache. Checks use your plan's lookups, its spending limit and the service's daily limits.\n- When your lookups or your optional spending limit run out, checks pause and the dashboard says why. Nothing is billed while paused. We try again about every 6 hours, and you can resume at once with POST /watchlists/resume.\n- A check compares the home with its last snapshot: price, status, Zestimate, rent Zestimate and photo count. Only those fields are kept.\n- Paused homes and paused watchlists aren't checked. After a downgrade, homes past the new plan's limit aren't checked until you remove some.\n- Events are kept for the dashboard and GET /watchlists/events. With a webhook URL set, each event is also sent to it; with the email digest on, you get one email a day listing the day's events.\n\nWebhooks:\n- Each event is a POST of JSON to your https webhook URL: { id, type, created_at, data: { watchlist_id, home_id, zpid, address, detail_url, old, new, change, change_pct } }. type is watch.<event type>, e.g. watch.price_change.\n- X-PropertiesAPI-Event-Id is the event id, the same on every retry: use it to ignore duplicates.\n- X-PropertiesAPI-Signature is t=<unix seconds>,v1=<hex HMAC-SHA256 of \"<t>.<raw body>\" with your signing secret>. Check it against the raw body and refuse timestamps older than 5 minutes.\n- Answer with any 2xx status. Anything else, or no answer within 10 seconds (DNS included), is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours, then marked failed in the delivery log.\n- An endpoint that answers slowly (5 seconds or more) or fails is backed off: your other events wait, starting at 1 minute and doubling on each slow or failed answer, up to 6 hours. A fast 2xx resets it. Events go out in the order they happened, at most 2 at a time per account.\n- The URL must be https on port 443 (or 8443) and point to a public internet address. Private, loopback, link-local and cloud metadata addresses are refused when you save it, and again before every delivery and every redirect.\n\nEvent types:\n- price_change: The price moved by at least the watchlist's price_change_pct (default 0: any change). A change between a rent and a sale price isn't counted.\n- status_change: The status moved between for_sale, pending, sold, off_market and for_rent. An unclear status never fires it.\n- zestimate_change: The Zestimate moved by at least zestimate_change_pct (default 1).\n- new_photos: The photo count went up.\n- rent_change: The rent Zestimate moved by at least rent_change_pct (default 0: any change).\n\nErrors:\n- 400 bad_param: A name outside 1-80 characters, a threshold outside 0-100, an unknown event type, or a url / zpid / address the lookup would refuse.\n- 400 bad_webhook_url: The webhook URL isn't https, has a user name or password, uses another port, doesn't resolve, or points to a private address. details.reason says which.\n- 402 credits_exhausted: Adding a home needs a lookup and your lookups are used up (as for GET /v1/zillow/property). Not billed.\n- 403 account_required: The key isn't a dashboard key from an account.\n- 403 watch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n- 403 too_many_watchlists: An account can have up to 20 watchlists.\n- 404 not_found: No such watchlist or watched home on your account.\n- 404 watchlists_disabled: Watchlists aren't available yet.\n- 409 already_watched: That watchlist already watches this home (the same zpid, link or address). Checked before any lookup: not billed.",
        "tags": [
          "Watchlists"
        ],
        "externalDocs": {
          "url": "https://propertiesapi.com/docs/watchlists"
        },
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK. The body is { data } (adds also have meta.credits and meta.cached).",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "402": {
            "$ref": "#/components/responses/E402"
          },
          "403": {
            "description": "account_required: The key isn't a dashboard key from an account.\n\nwatch_limit_reached: Your plan's watched-home limit is reached (details.limit, details.plan, details.upgrade_url). Checked before any lookup: not billed.\n\ntoo_many_watchlists: An account can have up to 20 watchlists.",
            "headers": {
              "x-api-version": {
                "$ref": "#/components/headers/x-api-version"
              },
              "cache-control": {
                "$ref": "#/components/headers/cache-control"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key"
      }
    },
    "headers": {
      "x-api-version": {
        "description": "Every response: always v1.",
        "schema": {
          "type": "string",
          "enum": [
            "v1"
          ]
        }
      },
      "cache-control": {
        "description": "Every response: no-store.",
        "schema": {
          "type": "string"
        }
      },
      "x-cache": {
        "description": "GET /v1/zillow/property successes only: HIT when served from the 24-hour cache (free), MISS when fetched live.",
        "schema": {
          "type": "string",
          "enum": [
            "HIT",
            "MISS"
          ]
        }
      },
      "x-ratelimit-limit": {
        "description": "Authenticated requests: your per-minute request limit (see Rate limits).",
        "schema": {
          "type": "integer"
        }
      },
      "x-ratelimit-remaining": {
        "description": "Authenticated requests: requests left this minute; zero on 429 rate_limited. It counts requests a minute only, not the daily lookup caps.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-credits-remaining": {
        "description": "Account (dashboard) keys: included lookups left this billing period; zero on 402 credits_exhausted and spend_limit_reached.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-credits-limit": {
        "description": "Account keys: included lookups this billing period.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-credits-used": {
        "description": "Account keys: included lookups used this billing period.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-credits-reset": {
        "description": "Account keys with a billing period: when it ends and the included lookups reset (ISO 8601).",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "x-credits-prepaid-remaining": {
        "description": "Account keys: prepaid (pay-as-you-go) lookups left, zero when you have none. They are used after the included lookups and before any usage past a plan.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-credits-overage-remaining": {
        "description": "Paid plans with usage past the plan on and an optional monthly spending limit set: lookups still allowed past the included ones before that limit. Absent when no limit is set (no cap).",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "x-spend-limit-usd": {
        "description": "Paid plans with an optional monthly spending limit set: the limit in US dollars (usage past the included lookups, at your plan's overage rate). Absent when no limit is set.",
        "schema": {
          "type": "string",
          "description": "Dollars with two decimals, e.g. 25.00."
        }
      },
      "retry-after": {
        "description": "Errors with a wait: 429 rate_limited, daily_share_exhausted and env_key_daily_cap; every 503 (upstream_busy, store_unavailable and live_lookups_capped are the ones to wait out; see Retries); 502 upstream_blocked. Seconds to wait before retrying. The same number is in details.retry_after.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "link": {
        "description": "On 402 credits_exhausted and spend_limit_reached: the dashboard's plan and billing page, as <url>; rel=\"payment\".",
        "schema": {
          "type": "string"
        }
      },
      "server-timing": {
        "description": "Lookup responses (property and batch, errors included): how long each step took on our side, in the standard Server-Timing format: one name;dur= entry per step (its duration in ms) and a total. For your own latency charts; the step names may change.",
        "schema": {
          "type": "string",
          "description": "W3C Server-Timing: one name;dur=<ms> entry per step, and total."
        }
      }
    },
    "responses": {
      "E400": {
        "description": "missing_param: None of url, address or zpid was sent.\n\nbad_param: More than one of url, address, zpid; url isn't a zillow.com property link (a home's /homedetails/, /homes/ or new-construction /community/ link with its zpid, or a /b/ or /apartments/ building link; sub-pages such as /photos/ after it are fine; search pages, other pages and links with more than one zpid are refused) or is too long; address is outside 5-200 characters, has no house number, or has neither a city and state nor a ZIP code (US addresses only; checked before any lookup, so it's free); zpid isn't digits; status isn't for_sale, sold or for_rent; raw isn't true or false.\n\nbad_body: Batch only: the body isn't JSON, or items is missing or empty.\n\ntoo_many_items: Batch only: more than 50 items.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E401": {
        "description": "missing_api_key: The x-api-key header is missing.\n\ninvalid_api_key: The key is unknown.\n\nrevoked_api_key: The key was revoked. Create a new one in your dashboard.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E402": {
        "description": "credits_exhausted: The account's lookups are used up: the Free plan's one-time lookups and any prepaid (pay-as-you-go) credits, or a paid plan's included lookups and prepaid credits while usage past the plan is off (it pauses while a payment is failing). Not billed, no lookup made. details.upgrade_url links to the plan and billing section of your dashboard, where you can choose a plan or buy prepaid credits. Cached properties still work.\n\nspend_limit_reached: Only if you set an optional monthly spending limit (there is none by default, and usage past the plan isn't capped): a paid plan went past its included lookups at its overage rate and reached that limit. Not billed, no lookup made. details has the limit (spend_limit_usd), your plan's overage rate, the overage used so far and dashboard_url, where you can raise or remove the limit. Cached properties still work; the limit resets with your billing period.\n\npayment_disputed: A payment for the account's plan was disputed or refunded, so live lookups on the plan and usage past it are paused until it is resolved; prepaid credits stay usable, and this answer means none are left. Not billed, no lookup made. Cached properties still work. details.dashboard_url; contact support to restore the plan.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E404": {
        "description": "not_found: No Zillow property matches the URL, address or zpid, or the page has no listing data to return. Not billed. details.reason may say why. A checked not-found, and a page with no listing data, is remembered for an hour: repeating the same input in that time returns the same not_found error at once, without a new lookup.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E408": {
        "description": "request_timeout: Batch only: the request body didn't arrive in time. Not billed. Retry.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E409": {
        "description": "zpid_mismatch: Zillow answered the link or zpid with a different home, and the one asked for couldn't be confirmed. Not billed, and nothing is cached. details.candidate has the other home's zpid and detailUrl. Check the link or zpid, or retry later. It isn't an address problem: it never comes as address_ambiguous.\n\naddress_ambiguous: Zillow's closest match for the address is a different home (another house number, street, directional, unit, ZIP, city or state). Not billed. details.candidate.detailUrl is that home's Zillow link and details.mismatch says what differed. Repeating the same address within an hour returns the same 409 without a new lookup. If the candidate is the home you meant, look it up with url=detailUrl (a normal, billed lookup); otherwise add the unit, city, state or ZIP.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E413": {
        "description": "property_response_too_large: This property's response would be larger than we can deliver (almost always because of raw=true on a home with a very large Zillow record). Not billed. Retry with raw=false.\n\nbatch_response_too_large: Batch only, per item: the batch's response reached its size limit, so this item was not added. Not billed. Retry it in a smaller batch, on its own, or with raw=false.\n\nbad_body: Batch only: the request body is over 64 KB. Send fewer items per request.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E422": {
        "description": "unprocessable_input: The upstream (the data collector) rejected this input. Check the Zillow URL or address.\n\nrental_input_missing: Rental analysis only: the home has no price (no list price, sold price or Zestimate) or no rent estimate, and the request didn't pass price or monthly_rent. details.missing names the input; nothing else about the home is returned. Not billed, and nothing is cached: asking again with the input is a normal lookup.\n\nupstream_unsupported_page: We can't look up this kind of Zillow link (for example a building or community page the self-hosted collector does not support, or a link the data collector refuses). Not billed. Use the home's zillow.com/homedetails/ link, its zpid or its address.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E429": {
        "description": "rate_limited: Too many requests a minute for your account: all its keys and batch items count together (see Rate limits). Wait retry-after seconds. Not billed.\n\ndaily_share_exhausted: A Free account made today's live lookups (see Rate limits), or the Free tier's shared daily budget is used. Cached properties still work; live lookups start again after midnight UTC (retry-after has the seconds until then). Not billed.\n\nenv_key_daily_cap: A legacy environment-list key used today's live-lookup allowance. Use a dashboard key, or wait retry-after seconds (until midnight UTC). Cached properties still work. Not billed.\n\ntoo_many_failed_lookups: Too many of the account's live lookups today found nothing or failed (they are not billed, but each one costs an upstream run). New uncached lookups pause until midnight UTC (retry-after); cached properties still work. Check the links, zpids or addresses you send.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E500": {
        "description": "internal: Something failed on our side. Safe to retry with backoff.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E502": {
        "description": "upstream_error: The upstream (the data collector) failed (also upstream_unreachable, upstream_bad_response, upstream_run_failed; and from the self-hosted collector upstream_response_too_large, upstream_bad_redirect, upstream_redirect_limit, job_expired and job_cancelled). Not billed. Safe to retry with backoff.\n\nupstream_blocked: Zillow denied access from the self-hosted collector. No data is returned or billed. Collection pauses; wait for retry-after and check collector readiness.\n\nupstream_schema_changed: The self-hosted collector could not read valid detail data from the page. No data is returned or billed.\n\nupstream_incomplete: An operator-required field was not supplied, or a listed home came back without its price or status. Nothing was billed for this result; missing_fields identifies what was missing. When details.retryable is true, a later retry may succeed; otherwise repeating unchanged source absence is not a recovery strategy.\n\nupstream_identity_mismatch: The self-hosted collector received a different property than requested and refused the result. Not billed.\n\nupstream_cost_cap: The lookup was stopped at its per-lookup upstream cost limit before it found the home. Not billed. Retry at most once, with status=for_sale, sold or for_rent if you know it.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E503": {
        "description": "upstream_busy: The upstream is busy (the data collector couldn't take the lookup). Wait for retry-after seconds. Other 503 codes (no_api_keys, live_lookups_disabled, upstream_auth, upstream_quota, upstream_misconfigured) mean live lookups are unavailable on our side, and retrying soon won't help. Every 503 from a lookup carries retry-after, so a client that waits it out doesn't hammer us.\n\nupstream_unavailable: Live lookups are paused for a while because our upstream refused new work (its monthly capacity is used up, or a credential needs fixing). Cached properties still work and nothing is billed. Retry after retry-after seconds; /status shows when lookups are back.\n\nlive_lookups_capped: Today's service-wide safety limit on live lookups is reached: our daily upstream budget (for paid plans, details.scope is paid_daily_budget). It is our limit, not one on your account, and nothing is billed. Cached properties still work; new lookups resume at 00:00 UTC (retry-after has the seconds until then).\n\nstore_unavailable: Our usage and cache store is unreachable, so live lookups are refused rather than run unmetered. Retry after retry-after seconds (30).\n\nstate_unavailable: The self-hosted collector can't take the lookup right now: its working state is unavailable (also storage_full: its storage is full). Not billed. Retry after retry-after seconds.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "E504": {
        "description": "upstream_timeout: The live lookup took too long. Not billed. Safe to retry.\n\nbatch_deadline: Batch only, per item: the batch ran out of time before this item started. Not billed. Retry it.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE400": {
        "description": "missing_param: None of url, address or zpid was sent.\n\nbad_param: More than one of url, address, zpid; url isn't a zillow.com property link (a home's /homedetails/, /homes/ or new-construction /community/ link with its zpid, or a /b/ or /apartments/ building link; sub-pages such as /photos/ after it are fine; search pages, other pages and links with more than one zpid are refused) or is too long; address is outside 5-200 characters, has no house number, or has neither a city and state nor a ZIP code (US addresses only; checked before any lookup, so it's free); zpid isn't digits; status isn't for_sale, sold or for_rent; raw isn't true or false.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE401": {
        "description": "missing_api_key: The x-api-key header is missing.\n\ninvalid_api_key: The key is unknown.\n\nrevoked_api_key: The key was revoked. Create a new one in your dashboard.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE402": {
        "description": "credits_exhausted: The account's lookups are used up: the Free plan's one-time lookups and any prepaid (pay-as-you-go) credits, or a paid plan's included lookups and prepaid credits while usage past the plan is off (it pauses while a payment is failing). Not billed, no lookup made. details.upgrade_url links to the plan and billing section of your dashboard, where you can choose a plan or buy prepaid credits. Cached properties still work.\n\nspend_limit_reached: Only if you set an optional monthly spending limit (there is none by default, and usage past the plan isn't capped): a paid plan went past its included lookups at its overage rate and reached that limit. Not billed, no lookup made. details has the limit (spend_limit_usd), your plan's overage rate, the overage used so far and dashboard_url, where you can raise or remove the limit. Cached properties still work; the limit resets with your billing period.\n\npayment_disputed: A payment for the account's plan was disputed or refunded, so live lookups on the plan and usage past it are paused until it is resolved; prepaid credits stay usable, and this answer means none are left. Not billed, no lookup made. Cached properties still work. details.dashboard_url; contact support to restore the plan.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "link": {
            "$ref": "#/components/headers/link"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE404": {
        "description": "not_found: No Zillow property matches the URL, address or zpid, or the page has no listing data to return. Not billed. details.reason may say why. A checked not-found, and a page with no listing data, is remembered for an hour: repeating the same input in that time returns the same not_found error at once, without a new lookup.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE409": {
        "description": "zpid_mismatch: Zillow answered the link or zpid with a different home, and the one asked for couldn't be confirmed. Not billed, and nothing is cached. details.candidate has the other home's zpid and detailUrl. Check the link or zpid, or retry later. It isn't an address problem: it never comes as address_ambiguous.\n\naddress_ambiguous: Zillow's closest match for the address is a different home (another house number, street, directional, unit, ZIP, city or state). Not billed. details.candidate.detailUrl is that home's Zillow link and details.mismatch says what differed. Repeating the same address within an hour returns the same 409 without a new lookup. If the candidate is the home you meant, look it up with url=detailUrl (a normal, billed lookup); otherwise add the unit, city, state or ZIP.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE413": {
        "description": "property_response_too_large: This property's response would be larger than we can deliver (almost always because of raw=true on a home with a very large Zillow record). Not billed. Retry with raw=false.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE422": {
        "description": "unprocessable_input: The upstream (the data collector) rejected this input. Check the Zillow URL or address.\n\nupstream_unsupported_page: We can't look up this kind of Zillow link (for example a building or community page the self-hosted collector does not support, or a link the data collector refuses). Not billed. Use the home's zillow.com/homedetails/ link, its zpid or its address.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE429": {
        "description": "rate_limited: Too many requests a minute for your account: all its keys and batch items count together (see Rate limits). Wait retry-after seconds. Not billed.\n\ndaily_share_exhausted: A Free account made today's live lookups (see Rate limits), or the Free tier's shared daily budget is used. Cached properties still work; live lookups start again after midnight UTC (retry-after has the seconds until then). Not billed.\n\nenv_key_daily_cap: A legacy environment-list key used today's live-lookup allowance. Use a dashboard key, or wait retry-after seconds (until midnight UTC). Cached properties still work. Not billed.\n\ntoo_many_failed_lookups: Too many of the account's live lookups today found nothing or failed (they are not billed, but each one costs an upstream run). New uncached lookups pause until midnight UTC (retry-after); cached properties still work. Check the links, zpids or addresses you send.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE500": {
        "description": "internal: Something failed on our side. Safe to retry with backoff.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE502": {
        "description": "upstream_error: The upstream (the data collector) failed (also upstream_unreachable, upstream_bad_response, upstream_run_failed; and from the self-hosted collector upstream_response_too_large, upstream_bad_redirect, upstream_redirect_limit, job_expired and job_cancelled). Not billed. Safe to retry with backoff.\n\nupstream_blocked: Zillow denied access from the self-hosted collector. No data is returned or billed. Collection pauses; wait for retry-after and check collector readiness.\n\nupstream_schema_changed: The self-hosted collector could not read valid detail data from the page. No data is returned or billed.\n\nupstream_incomplete: An operator-required field was not supplied, or a listed home came back without its price or status. Nothing was billed for this result; missing_fields identifies what was missing. When details.retryable is true, a later retry may succeed; otherwise repeating unchanged source absence is not a recovery strategy.\n\nupstream_identity_mismatch: The self-hosted collector received a different property than requested and refused the result. Not billed.\n\nupstream_cost_cap: The lookup was stopped at its per-lookup upstream cost limit before it found the home. Not billed. Retry at most once, with status=for_sale, sold or for_rent if you know it.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE503": {
        "description": "upstream_busy: The upstream is busy (the data collector couldn't take the lookup). Wait for retry-after seconds. Other 503 codes (no_api_keys, live_lookups_disabled, upstream_auth, upstream_quota, upstream_misconfigured) mean live lookups are unavailable on our side, and retrying soon won't help. Every 503 from a lookup carries retry-after, so a client that waits it out doesn't hammer us.\n\nupstream_unavailable: Live lookups are paused for a while because our upstream refused new work (its monthly capacity is used up, or a credential needs fixing). Cached properties still work and nothing is billed. Retry after retry-after seconds; /status shows when lookups are back.\n\nlive_lookups_capped: Today's service-wide safety limit on live lookups is reached: our daily upstream budget (for paid plans, details.scope is paid_daily_budget). It is our limit, not one on your account, and nothing is billed. Cached properties still work; new lookups resume at 00:00 UTC (retry-after has the seconds until then).\n\nstore_unavailable: Our usage and cache store is unreachable, so live lookups are refused rather than run unmetered. Retry after retry-after seconds (30).\n\nstate_unavailable: The self-hosted collector can't take the lookup right now: its working state is unavailable (also storage_full: its storage is full). Not billed. Retry after retry-after seconds.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PropertyE504": {
        "description": "upstream_timeout: The live lookup took too long. Not billed. Safe to retry.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE400": {
        "description": "bad_param: More than one of url, address, zpid; url isn't a zillow.com property link (a home's /homedetails/, /homes/ or new-construction /community/ link with its zpid, or a /b/ or /apartments/ building link; sub-pages such as /photos/ after it are fine; search pages, other pages and links with more than one zpid are refused) or is too long; address is outside 5-200 characters, has no house number, or has neither a city and state nor a ZIP code (US addresses only; checked before any lookup, so it's free); zpid isn't digits; status isn't for_sale, sold or for_rent; raw isn't true or false.\n\nbad_body: Batch only: the body isn't JSON, or items is missing or empty.\n\ntoo_many_items: Batch only: more than 50 items.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE401": {
        "description": "missing_api_key: The x-api-key header is missing.\n\ninvalid_api_key: The key is unknown.\n\nrevoked_api_key: The key was revoked. Create a new one in your dashboard.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE408": {
        "description": "request_timeout: Batch only: the request body didn't arrive in time. Not billed. Retry.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE413": {
        "description": "bad_body: Batch only: the request body is over 64 KB. Send fewer items per request.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE429": {
        "description": "rate_limited: Too many requests a minute for your account: all its keys and batch items count together (see Rate limits). Wait retry-after seconds. Not billed.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE500": {
        "description": "internal: Something failed on our side. Safe to retry with backoff.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BatchE503": {
        "description": "store_unavailable: Our usage and cache store is unreachable, so live lookups are refused rather than run unmetered. Retry after retry-after seconds (30).",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE400": {
        "description": "missing_param: None of url, address or zpid was sent.\n\nbad_param: More than one of url, address, zpid; url isn't a zillow.com property link (a home's /homedetails/, /homes/ or new-construction /community/ link with its zpid, or a /b/ or /apartments/ building link; sub-pages such as /photos/ after it are fine; search pages, other pages and links with more than one zpid are refused) or is too long; address is outside 5-200 characters, has no house number, or has neither a city and state nor a ZIP code (US addresses only; checked before any lookup, so it's free); zpid isn't digits; status isn't for_sale, sold or for_rent; raw isn't true or false.\n\nbad_body: Batch only: the body isn't JSON, or items is missing or empty.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE401": {
        "description": "missing_api_key: The x-api-key header is missing.\n\ninvalid_api_key: The key is unknown.\n\nrevoked_api_key: The key was revoked. Create a new one in your dashboard.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE402": {
        "description": "credits_exhausted: The account's lookups are used up: the Free plan's one-time lookups and any prepaid (pay-as-you-go) credits, or a paid plan's included lookups and prepaid credits while usage past the plan is off (it pauses while a payment is failing). Not billed, no lookup made. details.upgrade_url links to the plan and billing section of your dashboard, where you can choose a plan or buy prepaid credits. Cached properties still work.\n\nspend_limit_reached: Only if you set an optional monthly spending limit (there is none by default, and usage past the plan isn't capped): a paid plan went past its included lookups at its overage rate and reached that limit. Not billed, no lookup made. details has the limit (spend_limit_usd), your plan's overage rate, the overage used so far and dashboard_url, where you can raise or remove the limit. Cached properties still work; the limit resets with your billing period.\n\npayment_disputed: A payment for the account's plan was disputed or refunded, so live lookups on the plan and usage past it are paused until it is resolved; prepaid credits stay usable, and this answer means none are left. Not billed, no lookup made. Cached properties still work. details.dashboard_url; contact support to restore the plan.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "link": {
            "$ref": "#/components/headers/link"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE404": {
        "description": "not_found: No Zillow property matches the URL, address or zpid, or the page has no listing data to return. Not billed. details.reason may say why. A checked not-found, and a page with no listing data, is remembered for an hour: repeating the same input in that time returns the same not_found error at once, without a new lookup.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE408": {
        "description": "request_timeout: Batch only: the request body didn't arrive in time. Not billed. Retry.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE409": {
        "description": "zpid_mismatch: Zillow answered the link or zpid with a different home, and the one asked for couldn't be confirmed. Not billed, and nothing is cached. details.candidate has the other home's zpid and detailUrl. Check the link or zpid, or retry later. It isn't an address problem: it never comes as address_ambiguous.\n\naddress_ambiguous: Zillow's closest match for the address is a different home (another house number, street, directional, unit, ZIP, city or state). Not billed. details.candidate.detailUrl is that home's Zillow link and details.mismatch says what differed. Repeating the same address within an hour returns the same 409 without a new lookup. If the candidate is the home you meant, look it up with url=detailUrl (a normal, billed lookup); otherwise add the unit, city, state or ZIP.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE413": {
        "description": "property_response_too_large: This property's response would be larger than we can deliver (almost always because of raw=true on a home with a very large Zillow record). Not billed. Retry with raw=false.\n\nbad_body: Batch only: the request body is over 64 KB. Send fewer items per request.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE422": {
        "description": "unprocessable_input: The upstream (the data collector) rejected this input. Check the Zillow URL or address.\n\nrental_input_missing: Rental analysis only: the home has no price (no list price, sold price or Zestimate) or no rent estimate, and the request didn't pass price or monthly_rent. details.missing names the input; nothing else about the home is returned. Not billed, and nothing is cached: asking again with the input is a normal lookup.\n\nupstream_unsupported_page: We can't look up this kind of Zillow link (for example a building or community page the self-hosted collector does not support, or a link the data collector refuses). Not billed. Use the home's zillow.com/homedetails/ link, its zpid or its address.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE429": {
        "description": "rate_limited: Too many requests a minute for your account: all its keys and batch items count together (see Rate limits). Wait retry-after seconds. Not billed.\n\ndaily_share_exhausted: A Free account made today's live lookups (see Rate limits), or the Free tier's shared daily budget is used. Cached properties still work; live lookups start again after midnight UTC (retry-after has the seconds until then). Not billed.\n\nenv_key_daily_cap: A legacy environment-list key used today's live-lookup allowance. Use a dashboard key, or wait retry-after seconds (until midnight UTC). Cached properties still work. Not billed.\n\ntoo_many_failed_lookups: Too many of the account's live lookups today found nothing or failed (they are not billed, but each one costs an upstream run). New uncached lookups pause until midnight UTC (retry-after); cached properties still work. Check the links, zpids or addresses you send.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE500": {
        "description": "internal: Something failed on our side. Safe to retry with backoff.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE502": {
        "description": "upstream_error: The upstream (the data collector) failed (also upstream_unreachable, upstream_bad_response, upstream_run_failed; and from the self-hosted collector upstream_response_too_large, upstream_bad_redirect, upstream_redirect_limit, job_expired and job_cancelled). Not billed. Safe to retry with backoff.\n\nupstream_blocked: Zillow denied access from the self-hosted collector. No data is returned or billed. Collection pauses; wait for retry-after and check collector readiness.\n\nupstream_schema_changed: The self-hosted collector could not read valid detail data from the page. No data is returned or billed.\n\nupstream_incomplete: An operator-required field was not supplied, or a listed home came back without its price or status. Nothing was billed for this result; missing_fields identifies what was missing. When details.retryable is true, a later retry may succeed; otherwise repeating unchanged source absence is not a recovery strategy.\n\nupstream_identity_mismatch: The self-hosted collector received a different property than requested and refused the result. Not billed.\n\nupstream_cost_cap: The lookup was stopped at its per-lookup upstream cost limit before it found the home. Not billed. Retry at most once, with status=for_sale, sold or for_rent if you know it.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE503": {
        "description": "upstream_busy: The upstream is busy (the data collector couldn't take the lookup). Wait for retry-after seconds. Other 503 codes (no_api_keys, live_lookups_disabled, upstream_auth, upstream_quota, upstream_misconfigured) mean live lookups are unavailable on our side, and retrying soon won't help. Every 503 from a lookup carries retry-after, so a client that waits it out doesn't hammer us.\n\nupstream_unavailable: Live lookups are paused for a while because our upstream refused new work (its monthly capacity is used up, or a credential needs fixing). Cached properties still work and nothing is billed. Retry after retry-after seconds; /status shows when lookups are back.\n\nlive_lookups_capped: Today's service-wide safety limit on live lookups is reached: our daily upstream budget (for paid plans, details.scope is paid_daily_budget). It is our limit, not one on your account, and nothing is billed. Cached properties still work; new lookups resume at 00:00 UTC (retry-after has the seconds until then).\n\nstore_unavailable: Our usage and cache store is unreachable, so live lookups are refused rather than run unmetered. Retry after retry-after seconds (30).\n\nstate_unavailable: The self-hosted collector can't take the lookup right now: its working state is unavailable (also storage_full: its storage is full). Not billed. Retry after retry-after seconds.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          },
          "retry-after": {
            "$ref": "#/components/headers/retry-after"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RentalE504": {
        "description": "upstream_timeout: The live lookup took too long. Not billed. Safe to retry.",
        "headers": {
          "x-api-version": {
            "$ref": "#/components/headers/x-api-version"
          },
          "cache-control": {
            "$ref": "#/components/headers/cache-control"
          },
          "x-ratelimit-limit": {
            "$ref": "#/components/headers/x-ratelimit-limit"
          },
          "x-ratelimit-remaining": {
            "$ref": "#/components/headers/x-ratelimit-remaining"
          },
          "x-credits-remaining": {
            "$ref": "#/components/headers/x-credits-remaining"
          },
          "x-credits-limit": {
            "$ref": "#/components/headers/x-credits-limit"
          },
          "x-credits-used": {
            "$ref": "#/components/headers/x-credits-used"
          },
          "x-credits-reset": {
            "$ref": "#/components/headers/x-credits-reset"
          },
          "x-credits-prepaid-remaining": {
            "$ref": "#/components/headers/x-credits-prepaid-remaining"
          },
          "x-credits-overage-remaining": {
            "$ref": "#/components/headers/x-credits-overage-remaining"
          },
          "x-spend-limit-usd": {
            "$ref": "#/components/headers/x-spend-limit-usd"
          },
          "server-timing": {
            "$ref": "#/components/headers/server-timing"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "ZillowProperty": {
        "type": "object",
        "description": "One Zillow property (type ZillowProperty). Fields Zillow doesn't show for this property are null; lists may be empty. With raw=true, data also has raw (the unmapped source record; unstable, see raw).\n\nUsually null: price: The home isn't listed for sale or rent (an off-market home's Zestimate or tax value is never returned as its price), the listing doesn't show a price, or a sold home's sale price isn't disclosed (some states don't publish sale prices). datePosted: The home isn't currently listed. zestimate: Zillow doesn't show a Zestimate for this home. rentZestimate: Zillow doesn't show a rent Zestimate for this home. beds, baths, livingArea, lotSize, yearBuilt: The Zillow page doesn't list this fact (common for land and new construction). daysOnZillow: Zillow doesn't give a day count (common for apartment buildings). Sold and off-market homes usually still have one; it isn't days on market (see calculated.days_on_market). brokerName, agentName: The listing doesn't name a broker or agent, or the home isn't listed.",
        "required": [
          "zpid",
          "detailUrl",
          "address",
          "streetAddress",
          "unit",
          "city",
          "state",
          "zipcode",
          "county",
          "neighborhood",
          "latitude",
          "longitude",
          "price",
          "currency",
          "homeStatus",
          "statusText",
          "homeType",
          "beds",
          "baths",
          "livingArea",
          "lotSize",
          "yearBuilt",
          "pricePerSquareFoot",
          "zestimate",
          "rentZestimate",
          "daysOnZillow",
          "datePosted",
          "dateSold",
          "lastSoldPrice",
          "monthlyHoaFee",
          "taxAnnualAmount",
          "taxAssessedValue",
          "propertyTaxRate",
          "pageViewCount",
          "favoriteCount",
          "parcelId",
          "description",
          "imgSrc",
          "photos",
          "photoCount",
          "brokerName",
          "brokerPhone",
          "agentName",
          "agentPhone",
          "agentLicenseNumber",
          "mlsId",
          "mlsName",
          "priceHistory",
          "taxHistory",
          "schools",
          "virtualTourUrl",
          "scrapedAt",
          "calculated"
        ],
        "properties": {
          "zpid": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zillow property ID. null for an apartment building (a zillow.com/apartments/ link): a building has no zpid of its own, so use its detailUrl as its id."
          },
          "detailUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Zillow property page."
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "description": "One-line address."
          },
          "streetAddress": {
            "type": [
              "string",
              "null"
            ],
            "description": "Street line."
          },
          "unit": {
            "type": [
              "string",
              "null"
            ],
            "description": "Unit number, e.g. 1905 for 600 N Fairbanks Ct APT 1905. For an apartment building, the unit that price describes (see price)."
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "City."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "Two-letter state code."
          },
          "zipcode": {
            "type": [
              "string",
              "null"
            ],
            "description": "ZIP code."
          },
          "county": {
            "type": [
              "string",
              "null"
            ],
            "description": "County."
          },
          "neighborhood": {
            "type": [
              "string",
              "null"
            ],
            "description": "Neighborhood name."
          },
          "latitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Latitude."
          },
          "longitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Longitude."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "List price. For rentals, the monthly rent. For an apartment building (a zillow.com/apartments/ link), the rent of its lowest-priced unit available now (no move-in date, or one on or before the lookup date; if none is available yet, its lowest-priced unit); beds, baths, livingArea and unit describe that same unit. For sold homes, the sale price, or null when the sale price isn't disclosed. Never 0."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "description": "Currency of price, e.g. USD."
          },
          "homeStatus": {
            "type": [
              "string",
              "null"
            ],
            "description": "Zillow's status code: FOR_SALE, FOR_RENT, RECENTLY_SOLD, PENDING, COMING_SOON, FORECLOSURE, OFF_MARKET or OTHER. Homes that aren't listed usually come back as OTHER."
          },
          "statusText": {
            "type": [
              "string",
              "null"
            ],
            "description": "A short readable status built from Zillow's status and listing flags: For sale, Pending, Coming soon, New construction, For sale by owner, Foreclosure, Auction, For rent, Sold or Off market. Null when the status is unclear."
          },
          "homeType": {
            "type": [
              "string",
              "null"
            ],
            "description": "SINGLE_FAMILY, CONDO, TOWNHOUSE, MULTI_FAMILY, APARTMENT, LOT, MANUFACTURED, ..."
          },
          "beds": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bedrooms."
          },
          "baths": {
            "type": [
              "number",
              "null"
            ],
            "description": "Bathrooms."
          },
          "livingArea": {
            "type": [
              "number",
              "null"
            ],
            "description": "Living area in square feet."
          },
          "lotSize": {
            "type": [
              "number",
              "null"
            ],
            "description": "Lot size in square feet (acres converted)."
          },
          "yearBuilt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Year built."
          },
          "pricePerSquareFoot": {
            "type": [
              "number",
              "null"
            ],
            "description": "Price per square foot."
          },
          "zestimate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Zillow's Zestimate, when shown."
          },
          "rentZestimate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Zillow's rent Zestimate (a month), when shown."
          },
          "daysOnZillow": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Zillow's day count for the home. For a listed home, the days the current listing has been on Zillow. Sold and off-market homes usually still have a count (counted from an earlier listing or event), which isn't days on market: use calculated.days_on_market, which is null unless the home is listed."
          },
          "datePosted": {
            "type": [
              "string",
              "null"
            ],
            "description": "Date the current listing went on the market (the day daysOnZillow counts from), YYYY-MM-DD. Null when the home isn't listed (sold or off market)."
          },
          "dateSold": {
            "type": [
              "string",
              "null"
            ],
            "description": "Date of the last sale, YYYY-MM-DD."
          },
          "lastSoldPrice": {
            "type": [
              "number",
              "null"
            ],
            "description": "Last sale price."
          },
          "monthlyHoaFee": {
            "type": [
              "number",
              "null"
            ],
            "description": "HOA fee a month."
          },
          "taxAnnualAmount": {
            "type": [
              "number",
              "null"
            ],
            "description": "Annual property tax."
          },
          "taxAssessedValue": {
            "type": [
              "number",
              "null"
            ],
            "description": "Assessed value."
          },
          "propertyTaxRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Property tax rate, percent."
          },
          "pageViewCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Cumulative views supplied by the source. Zero is meaningful; null when not returned."
          },
          "favoriteCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Favorites supplied by the source. Zero is meaningful; null when not returned."
          },
          "parcelId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Parcel (APN) number."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Listing description."
          },
          "imgSrc": {
            "type": [
              "string",
              "null"
            ],
            "description": "Main photo URL."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            },
            "description": "Photo URLs as published on the listing. Never re-hosted. Can be empty."
          },
          "photoCount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Number of photos."
          },
          "brokerName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Listing brokerage."
          },
          "brokerPhone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Brokerage phone."
          },
          "agentName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Listing agent."
          },
          "agentPhone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agent phone."
          },
          "agentLicenseNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "Agent license number."
          },
          "mlsId": {
            "type": [
              "string",
              "null"
            ],
            "description": "MLS listing number."
          },
          "mlsName": {
            "type": [
              "string",
              "null"
            ],
            "description": "MLS name."
          },
          "priceHistory": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ZillowPriceEvent"
            },
            "description": "Past events, newest first: date, event, price, pricePerSquareFoot, priceChangeRate, source. priceChangeRate is the change from the previous priced event of the same kind (sale or rent), as a fraction (−0.05 = 5% lower), and 0 when there is none; null when it can't be determined. Can be empty."
          },
          "taxHistory": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ZillowTaxEvent"
            },
            "description": "Tax years: year, taxPaid, value (assessed), taxIncreaseRate, valueIncreaseRate. Can be empty."
          },
          "schools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ZillowSchool"
            },
            "description": "Nearby schools: name, rating, distance (miles), level, type, grades, link. Can be empty."
          },
          "virtualTourUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Virtual tour link."
          },
          "scrapedAt": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the data was collected (ISO 8601)."
          },
          "calculated": {
            "$ref": "#/components/schemas/CalculatedFields"
          },
          "raw": {
            "description": "Only with raw=true. UNSTABLE, not part of the contract: the unmapped record of whichever source served the lookup, so its keys and casing (camelCase or snake_case) differ between lookups, even of the same home, and change without notice. Don't parse it; use the documented fields."
          }
        }
      },
      "CalculatedFields": {
        "type": "object",
        "description": "Calculated by PropertiesAPI from the returned fields; not Zillow estimates. They are plain arithmetic on the fields in the same response, never other data. Any figure whose inputs are missing, zero or don't apply to the home's status is null. Money is in USD rounded to cents, percentages to 2 decimals and ratios to 4 decimals.",
        "required": [
          "price_per_sqft",
          "zestimate_per_sqft",
          "rent_to_price_ratio",
          "rent_to_price_basis",
          "gross_yield_pct",
          "price_vs_zestimate_pct",
          "last_sale_price",
          "last_sale_date",
          "change_since_last_sale_pct",
          "change_since_last_sale_basis",
          "days_on_market",
          "price_drops_count",
          "last_price_change"
        ],
        "properties": {
          "price_per_sqft": {
            "type": [
              "number",
              "null"
            ],
            "description": "price ÷ livingArea. Only when price is a sale or asking price: null for rentals (price is a monthly rent) and when the status is unknown."
          },
          "zestimate_per_sqft": {
            "type": [
              "number",
              "null"
            ],
            "description": "zestimate ÷ livingArea."
          },
          "rent_to_price_ratio": {
            "type": [
              "number",
              "null"
            ],
            "description": "rentZestimate × 12 ÷ price, or ÷ zestimate when there is no price (and always for rentals, whose price is a rent). rent_to_price_basis says which was used."
          },
          "rent_to_price_basis": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "price",
              "zestimate",
              null
            ],
            "description": "price or zestimate: the denominator of rent_to_price_ratio. Null when the ratio is null."
          },
          "gross_yield_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "rent_to_price_ratio × 100: the rent Zestimate for a year as a percent of the price (or Zestimate). Before vacancy, taxes, insurance and other costs."
          },
          "price_vs_zestimate_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "(price − zestimate) ÷ zestimate × 100. Negative means the price is below the Zestimate. Only when price is a sale or asking price."
          },
          "last_sale_price": {
            "type": [
              "number",
              "null"
            ],
            "description": "The price of the most recent Sold event in priceHistory. Null when that sale's price isn't disclosed (an older sale's price is never used instead)."
          },
          "last_sale_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "The date of the most recent Sold event in priceHistory, YYYY-MM-DD."
          },
          "change_since_last_sale_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "(value now − last_sale_price) ÷ last_sale_price × 100, where value now is the asking price for a home listed for sale (for sale, pending, coming soon, foreclosure), otherwise the zestimate. change_since_last_sale_basis says which was used."
          },
          "change_since_last_sale_basis": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "price",
              "zestimate",
              null
            ],
            "description": "price or zestimate: the value now used by change_since_last_sale_pct. Null when that figure is null."
          },
          "days_on_market": {
            "type": [
              "integer",
              "null"
            ],
            "description": "daysOnZillow, only while the home is listed (for sale, pending, coming soon, foreclosure or for rent). Null otherwise: for a sold or off-market home Zillow's day count isn't days on market. Zillow's count can run across a relist, so it can cover a longer stretch than price_drops_count, which counts only the most recent listing."
          },
          "price_drops_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "In the most recent listing of the home's kind (from the last Listed for rent event for a rental, otherwise the last Listed for sale event, in priceHistory), the number of Price change events whose price is lower than the event before it in that listing. Events named for the other kind (a Listed for rent inside a sale listing) are skipped. Null when priceHistory has no such listing event."
          },
          "last_price_change": {
            "type": [
              "object",
              "null"
            ],
            "description": "The latest Price change event in that same listing: { date, price, previous_price, change, change_pct }, where previous_price is the price of the event before it in that listing (never one from before the listing started, never one of the other kind), change = price − previous_price and change_pct = change ÷ previous_price × 100. Null when there is none.",
            "properties": {
              "date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "YYYY-MM-DD",
                "format": "date"
              },
              "price": {
                "type": "number"
              },
              "previous_price": {
                "type": "number"
              },
              "change": {
                "type": "number"
              },
              "change_pct": {
                "type": "number"
              }
            }
          }
        }
      },
      "ZillowPriceEvent": {
        "type": "object",
        "properties": {
          "date": {
            "type": [
              "string",
              "null"
            ],
            "description": "YYYY-MM-DD",
            "format": "date"
          },
          "event": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. Listed for sale, Sold, Price change."
          },
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD"
          },
          "pricePerSquareFoot": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD"
          },
          "priceChangeRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Change vs the previous event."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. the MLS."
          }
        }
      },
      "ZillowTaxEvent": {
        "type": "object",
        "properties": {
          "year": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Tax year."
          },
          "taxPaid": {
            "type": [
              "number",
              "null"
            ],
            "description": "USD"
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "Assessed value, USD."
          },
          "taxIncreaseRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Change vs the previous year."
          },
          "valueIncreaseRate": {
            "type": [
              "number",
              "null"
            ],
            "description": "Change vs the previous year."
          }
        }
      },
      "ZillowSchool": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "School name."
          },
          "rating": {
            "type": [
              "number",
              "null"
            ],
            "description": "Rating."
          },
          "distance": {
            "type": [
              "number",
              "null"
            ],
            "description": "Miles."
          },
          "level": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. Elementary."
          },
          "type": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. Public."
          },
          "grades": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. PK-5."
          },
          "link": {
            "type": [
              "string",
              "null"
            ],
            "description": "School page."
          }
        }
      },
      "PropertyMeta": {
        "type": "object",
        "required": [
          "credits",
          "cached",
          "cache_key",
          "fetched_at",
          "source",
          "release",
          "took_ms",
          "completeness"
        ],
        "properties": {
          "credits": {
            "type": "integer",
            "description": "Credits billed for this lookup: 0 on a cache hit."
          },
          "cached": {
            "type": "boolean",
            "description": "True when served from the 24-hour cache."
          },
          "cache_key": {
            "type": "string",
            "description": "e.g. zpid:2064142765 or addr:..."
          },
          "fetched_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the data was collected from Zillow (a cache hit keeps the original time). When meta.source_age is \"approximate\", this is when we received the answer: the source may have served a copy it collected earlier, so the data can be a few hours older (sometimes more)."
          },
          "source_age": {
            "type": "string",
            "enum": [
              "approximate"
            ],
            "description": "Present only when fetched_at is approximate: the source served a copy it may have collected earlier, so the data can be older than fetched_at. Such answers stay in our cache for a shorter time."
          },
          "source": {
            "type": "object",
            "required": [
              "note"
            ],
            "properties": {
              "note": {
                "type": "string",
                "description": "Where the data comes from: the property's public Zillow page."
              }
            }
          },
          "release": {
            "type": "string",
            "enum": [
              "v1"
            ],
            "description": "The API release that produced this response."
          },
          "took_ms": {
            "type": "integer"
          },
          "completeness": {
            "type": "object",
            "description": "Observed response availability, not an upstream completeness guarantee: which of the commonly missing fields this response supplied, returned as null (not_returned) or returned as an empty list (empty_array, with count). Which fields are filled can depend on how the home was first looked up (link, zpid or address). Optional server-required data policy refuses unmet fields before billing.",
            "required": [
              "fields",
              "required",
              "missing",
              "requirements_satisfied"
            ],
            "properties": {
              "fields": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "required": [
                    "availability"
                  ],
                  "properties": {
                    "availability": {
                      "type": "string",
                      "enum": [
                        "supplied",
                        "not_returned",
                        "empty_array"
                      ]
                    },
                    "count": {
                      "type": "integer",
                      "minimum": 0
                    }
                  }
                }
              },
              "required": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "requirements_satisfied": {
                "type": "boolean"
              }
            }
          },
          "warning": {
            "type": "string",
            "description": "Present only when something needs checking. Zillow sometimes answers a link or zpid with a different home. Usually the lookup is then refused with 409 zpid_mismatch (not billed). When our fallback source returns that other home (or a building, zpid null), it is served with this warning instead: it is billed as a normal lookup and cached under its own key, never under the one you asked for. Check data.zpid. It is also set when the home returned doesn't have the status you gave in status (a hint, not a filter), and, with credits 0, when billing for the lookup couldn't be confirmed: your dashboard usage is authoritative."
          }
        }
      },
      "RentalAnalysisRequest": {
        "type": "object",
        "description": "Exactly one of url, zpid or address, optional status, and any rental inputs as overrides. At most 16 KB of JSON.",
        "properties": {
          "url": {
            "type": "string"
          },
          "address": {
            "type": "string"
          },
          "zpid": {
            "type": [
              "string",
              "integer"
            ],
            "pattern": "^\\s*\\d{1,12}\\s*$"
          },
          "status": {
            "type": "string",
            "enum": [
              "for_sale",
              "sold",
              "for_rent"
            ]
          },
          "price": {
            "type": [
              "number",
              "string"
            ],
            "description": "Purchase price, USD. Required. Auto-filled from the list or sold price, else Zillow's Zestimate. A JSON number (or a numeric string).",
            "maximum": 1000000000,
            "exclusiveMinimum": 0
          },
          "down_payment_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Down payment, percent of the price. Default 20% (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 20,
            "maximum": 100,
            "minimum": 0
          },
          "interest_rate_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Interest rate, percent a year. Default 7% a year: assumed; edit it. Not a quoted rate. A JSON number (or a numeric string).",
            "default": 7,
            "maximum": 30,
            "minimum": 0
          },
          "loan_term_years": {
            "type": [
              "integer",
              "string"
            ],
            "description": "Loan term, years. Default 30 years (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 30,
            "maximum": 50,
            "minimum": 1
          },
          "closing_costs_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Closing costs, percent of the price. Default 3% of the price (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 3,
            "maximum": 20,
            "minimum": 0
          },
          "monthly_rent": {
            "type": [
              "number",
              "string"
            ],
            "description": "Monthly rent, USD. Required. Auto-filled from Zillow's rent Zestimate (an estimate). A JSON number (or a numeric string).",
            "maximum": 10000000,
            "minimum": 0
          },
          "vacancy_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Vacancy, percent. Default 5% of gross rent (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 5,
            "maximum": 100,
            "minimum": 0
          },
          "management_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Property management, percent. Default 8% of rent collected (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 8,
            "maximum": 100,
            "minimum": 0
          },
          "maintenance_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Maintenance, percent. Default 5% of gross rent (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 5,
            "maximum": 100,
            "minimum": 0
          },
          "capex_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Capital expenditures, percent. Default 5% of gross rent, set aside for big repairs (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 5,
            "maximum": 100,
            "minimum": 0
          },
          "annual_property_tax": {
            "type": [
              "number",
              "string"
            ],
            "description": "Property tax a year, USD. Auto-filled from the tax history when there is one; otherwise an estimate of 1% of the price a year (edit it). A JSON number (or a numeric string).",
            "maximum": 100000000,
            "minimum": 0
          },
          "annual_insurance": {
            "type": [
              "number",
              "string"
            ],
            "description": "Insurance a year, USD. An estimate of 0.5% of the price a year, not a quote (edit it). A JSON number (or a numeric string).",
            "maximum": 100000000,
            "minimum": 0
          },
          "monthly_hoa": {
            "type": [
              "number",
              "string"
            ],
            "description": "HOA a month, USD. Auto-filled from the listing's HOA fee; default 0. A JSON number (or a numeric string).",
            "default": 0,
            "maximum": 10000000,
            "minimum": 0
          },
          "other_monthly_expenses": {
            "type": [
              "number",
              "string"
            ],
            "description": "Other costs a month, USD. Utilities you pay, landscaping and so on; default 0. A JSON number (or a numeric string).",
            "default": 0,
            "maximum": 10000000,
            "minimum": 0
          },
          "rent_growth_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Rent growth, percent a year. Projection only. Default 3% a year (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 3,
            "maximum": 50,
            "minimum": -20
          },
          "appreciation_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Appreciation, percent a year. Projection only. Default 3% a year (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 3,
            "maximum": 50,
            "minimum": -20
          },
          "expense_growth_pct": {
            "type": [
              "number",
              "string"
            ],
            "description": "Expense growth, percent a year. Projection only: taxes, insurance, HOA and other costs. Default 3% a year (an assumption; edit it). A JSON number (or a numeric string).",
            "default": 3,
            "maximum": 50,
            "minimum": -20
          }
        },
        "oneOf": [
          {
            "required": [
              "url"
            ]
          },
          {
            "required": [
              "zpid"
            ]
          },
          {
            "required": [
              "address"
            ]
          }
        ]
      },
      "RentalInput": {
        "type": "object",
        "required": [
          "value",
          "source"
        ],
        "properties": {
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "The value used."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "property",
              "default",
              "request",
              null
            ],
            "description": "property: read from this home (basis names the field); default: a documented assumption or estimate (note); request: what you passed."
          },
          "basis": {
            "type": "string",
            "description": "With source property: the field used, e.g. list price, Zestimate, rent Zestimate, tax history (2025)."
          },
          "note": {
            "type": "string",
            "description": "With source default: what the default is."
          }
        }
      },
      "RentalAnalysisResponse": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "property",
              "inputs",
              "metrics",
              "projection",
              "warnings",
              "disclaimer"
            ],
            "properties": {
              "property": {
                "type": "object",
                "description": "The home the analysis used: zpid, address, detailUrl, homeStatus, homeType, beds, baths, livingArea, yearBuilt, price, zestimate, rentZestimate, monthlyHoaFee, taxAnnualAmount, taxAssessedValue and propertyTaxRate, as GET /v1/zillow/property returns them (null when Zillow doesn't show them)."
              },
              "inputs": {
                "type": "object",
                "description": "One entry per input (the parameters above): value; source (property: read from this home, with basis naming the field used; default: a documented assumption or estimate, with note; request: the value you passed); basis or note.",
                "required": [
                  "price",
                  "down_payment_pct",
                  "interest_rate_pct",
                  "loan_term_years",
                  "closing_costs_pct",
                  "monthly_rent",
                  "vacancy_pct",
                  "management_pct",
                  "maintenance_pct",
                  "capex_pct",
                  "annual_property_tax",
                  "annual_insurance",
                  "monthly_hoa",
                  "other_monthly_expenses",
                  "rent_growth_pct",
                  "appreciation_pct",
                  "expense_growth_pct"
                ],
                "properties": {
                  "price": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "down_payment_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "interest_rate_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "loan_term_years": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "closing_costs_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "monthly_rent": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "vacancy_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "management_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "maintenance_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "capex_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "annual_property_tax": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "annual_insurance": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "monthly_hoa": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "other_monthly_expenses": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "rent_growth_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "appreciation_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  },
                  "expense_growth_pct": {
                    "$ref": "#/components/schemas/RentalInput"
                  }
                },
                "additionalProperties": false
              },
              "metrics": {
                "type": "object",
                "required": [
                  "down_payment",
                  "loan_amount",
                  "closing_costs",
                  "total_cash_needed",
                  "monthly_principal_and_interest",
                  "annual_debt_service",
                  "gross_monthly_rent",
                  "gross_annual_income",
                  "vacancy_loss",
                  "effective_gross_income",
                  "operating_expenses",
                  "total_operating_expenses",
                  "noi",
                  "annual_cash_flow",
                  "monthly_cash_flow",
                  "cap_rate_pct",
                  "cash_on_cash_pct",
                  "dscr",
                  "grm",
                  "one_percent_rule",
                  "break_even_rent"
                ],
                "properties": {
                  "down_payment": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "price x down_payment_pct."
                  },
                  "loan_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "price - down_payment."
                  },
                  "closing_costs": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "price x closing_costs_pct."
                  },
                  "total_cash_needed": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "down_payment + closing_costs."
                  },
                  "monthly_principal_and_interest": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "loan_amount x i / (1 - (1 + i)^-n), with i = interest_rate_pct / 12 and n = loan_term_years x 12. At a 0% rate, loan_amount / n; with no loan, 0."
                  },
                  "annual_debt_service": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "monthly_principal_and_interest x 12."
                  },
                  "gross_monthly_rent": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "monthly_rent."
                  },
                  "gross_annual_income": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "monthly_rent x 12."
                  },
                  "vacancy_loss": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "gross_annual_income x vacancy_pct."
                  },
                  "effective_gross_income": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "gross_annual_income - vacancy_loss (EGI)."
                  },
                  "operating_expenses": {
                    "type": "object",
                    "description": "management (EGI x management_pct), maintenance (gross x maintenance_pct), capex (gross x capex_pct), property_tax, insurance, hoa (monthly_hoa x 12) and other (other_monthly_expenses x 12), a year."
                  },
                  "total_operating_expenses": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "The sum of operating_expenses."
                  },
                  "noi": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Net operating income: effective_gross_income - total_operating_expenses (before the loan payment)."
                  },
                  "annual_cash_flow": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "noi - annual_debt_service."
                  },
                  "monthly_cash_flow": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "annual_cash_flow / 12."
                  },
                  "cap_rate_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "noi / price, percent."
                  },
                  "cash_on_cash_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "annual_cash_flow / total_cash_needed, percent. null when nothing is invested (0% down and no closing costs)."
                  },
                  "dscr": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Debt service coverage ratio: noi / annual_debt_service. null with no loan."
                  },
                  "grm": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Gross rent multiplier: price / gross_annual_income. null with no rent."
                  },
                  "one_percent_rule": {
                    "type": "object",
                    "description": "passes: monthly_rent is at least 1% of price; rent_to_price_pct: monthly_rent / price, percent."
                  },
                  "break_even_rent": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "The monthly rent at which annual_cash_flow is 0: (fixed costs + annual_debt_service) / (12 x [(1 - vacancy)(1 - management) - maintenance - capex]), fixed costs = tax + insurance + HOA and other costs a year. null when no rent can break even (for example 100% vacancy)."
                  }
                }
              },
              "projection": {
                "type": "array",
                "minItems": 5,
                "maxItems": 5,
                "description": "Five years: year, monthly_rent (grows by rent_growth_pct from year 2), noi (percentage costs follow the rent; tax, insurance, HOA and other costs grow by expense_growth_pct), annual_cash_flow (noi minus the loan payments still due that year: none once a short loan is paid off), cumulative_cash_flow, property_value (price x (1 + appreciation_pct)^year), loan_balance (after 12 x year payments), equity (property_value - loan_balance, before selling costs), total_profit (cumulative_cash_flow + equity - total_cash_needed) and total_return_pct (total_profit / total_cash_needed; null when nothing is invested).",
                "items": {
                  "type": "object",
                  "required": [
                    "year",
                    "monthly_rent",
                    "noi",
                    "annual_cash_flow",
                    "cumulative_cash_flow",
                    "property_value",
                    "loan_balance",
                    "equity",
                    "total_profit",
                    "total_return_pct"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer"
                    },
                    "monthly_rent": {
                      "type": "number"
                    },
                    "noi": {
                      "type": "number"
                    },
                    "annual_cash_flow": {
                      "type": "number"
                    },
                    "cumulative_cash_flow": {
                      "type": "number"
                    },
                    "property_value": {
                      "type": "number"
                    },
                    "loan_balance": {
                      "type": "number"
                    },
                    "equity": {
                      "type": "number"
                    },
                    "total_profit": {
                      "type": "number"
                    },
                    "total_return_pct": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "warnings": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Inputs to check: an assumed interest rate, estimated insurance or taxes, a Zestimate used as the price, a rent Zestimate used as the rent, a missing HOA fee on a condo, a negative cash flow. Can be empty."
              },
              "disclaimer": {
                "type": "string"
              }
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "credits",
              "cached",
              "cache_key",
              "fetched_at",
              "source",
              "release",
              "took_ms"
            ],
            "properties": {
              "credits": {
                "type": "integer",
                "description": "Credits billed for the lookup: 0 on a cache hit."
              },
              "cached": {
                "type": "boolean"
              },
              "cache_key": {
                "type": "string"
              },
              "fetched_at": {
                "type": "string",
                "format": "date-time"
              },
              "source": {
                "type": "object",
                "required": [
                  "note"
                ],
                "properties": {
                  "note": {
                    "type": "string"
                  }
                }
              },
              "release": {
                "type": "string",
                "enum": [
                  "v1"
                ]
              },
              "took_ms": {
                "type": "integer"
              },
              "warning": {
                "type": "string",
                "description": "Present only when something needs checking. Zillow sometimes answers a link or zpid with a different home. Usually the lookup is then refused with 409 zpid_mismatch (not billed). When our fallback source returns that other home (or a building, zpid null), it is served with this warning instead: it is billed as a normal lookup and cached under its own key, never under the one you asked for. Check data.zpid. It is also set when the home returned doesn't have the status you gave in status (a hint, not a filter), and, with credits 0, when billing for the lookup couldn't be confirmed: your dashboard usage is authoritative."
              }
            }
          }
        }
      },
      "PropertyResponse": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/ZillowProperty"
          },
          "meta": {
            "$ref": "#/components/schemas/PropertyMeta"
          }
        }
      },
      "ErrorBody": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "See the error table. Upstream codes may be added.",
            "examples": [
              "missing_param",
              "bad_param",
              "bad_body",
              "too_many_items",
              "missing_api_key",
              "invalid_api_key",
              "revoked_api_key",
              "credits_exhausted",
              "spend_limit_reached",
              "payment_disputed",
              "not_found",
              "request_timeout",
              "zpid_mismatch",
              "address_ambiguous",
              "property_response_too_large",
              "batch_response_too_large",
              "unprocessable_input",
              "rental_input_missing",
              "rate_limited",
              "daily_share_exhausted",
              "env_key_daily_cap",
              "too_many_failed_lookups",
              "internal",
              "upstream_error",
              "upstream_blocked",
              "upstream_schema_changed",
              "upstream_incomplete",
              "upstream_identity_mismatch",
              "upstream_unsupported_page",
              "upstream_cost_cap",
              "upstream_busy",
              "upstream_unavailable",
              "live_lookups_capped",
              "store_unavailable",
              "state_unavailable",
              "upstream_timeout",
              "batch_deadline"
            ]
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Code-specific: e.g. candidate (409), upgrade_url (402), retry_after (the same seconds as the retry-after header), missing_fields (upstream_incomplete)."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "meta"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody"
          },
          "meta": {
            "type": "object",
            "properties": {
              "took_ms": {
                "type": "integer"
              }
            }
          }
        },
        "example": {
          "error": {
            "code": "not_found",
            "message": "No Zillow property matches this input"
          },
          "meta": {
            "took_ms": 11840
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "description": "At most 64 KB of JSON (413 bad_body over it).",
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "description": "A zillow.com link, an address, or a zpid (1-12 digits)."
                },
                {
                  "type": "object",
                  "description": "Exactly one of url, address or zpid (an item with none or several fails on its own with missing_param or bad_param).",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "address": {
                      "type": "string"
                    },
                    "zpid": {
                      "type": [
                        "string",
                        "integer"
                      ],
                      "pattern": "^\\s*\\d{1,12}\\s*$"
                    }
                  },
                  "oneOf": [
                    {
                      "required": [
                        "url"
                      ]
                    },
                    {
                      "required": [
                        "address"
                      ]
                    },
                    {
                      "required": [
                        "zpid"
                      ]
                    }
                  ]
                }
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "for_sale",
              "sold",
              "for_rent"
            ],
            "description": "Applies to every item; a hint, as for GET /v1/zillow/property."
          },
          "raw": {
            "type": "boolean",
            "default": false,
            "description": "Only the JSON boolean true adds data.raw (unstable); any other value, such as the string \"true\", is ignored."
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "One result per item, same order.",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "description": "A successful item: the same data and meta as GET /v1/zillow/property.",
                  "required": [
                    "index",
                    "ok",
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "index": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "ok": {
                      "const": true
                    },
                    "data": {
                      "$ref": "#/components/schemas/ZillowProperty"
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PropertyMeta"
                    }
                  }
                },
                {
                  "type": "object",
                  "description": "A failed item (not billed). It has no HTTP status of its own; the code's status in the error table applies:\n\n400 missing_param, 400 bad_param, 402 credits_exhausted, 402 spend_limit_reached, 402 payment_disputed, 404 not_found, 409 zpid_mismatch, 409 address_ambiguous, 413 property_response_too_large, 413 batch_response_too_large, 422 unprocessable_input, 429 rate_limited, 429 daily_share_exhausted, 429 env_key_daily_cap, 429 too_many_failed_lookups, 500 internal, 502 upstream_error, 502 upstream_blocked, 502 upstream_schema_changed, 502 upstream_incomplete, 502 upstream_identity_mismatch, 422 upstream_unsupported_page, 502 upstream_cost_cap, 503 upstream_busy, 503 upstream_unavailable, 503 live_lookups_capped, 503 store_unavailable, 503 state_unavailable, 504 upstream_timeout, 504 batch_deadline.",
                  "required": [
                    "index",
                    "ok",
                    "error"
                  ],
                  "properties": {
                    "index": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "ok": {
                      "const": false
                    },
                    "error": {
                      "$ref": "#/components/schemas/ErrorBody"
                    }
                  }
                }
              ]
            }
          },
          "meta": {
            "type": "object",
            "required": [
              "count",
              "succeeded",
              "failed",
              "cache_hits",
              "credits",
              "release",
              "took_ms"
            ],
            "properties": {
              "count": {
                "type": "integer"
              },
              "succeeded": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "cache_hits": {
                "type": "integer"
              },
              "credits": {
                "type": "integer",
                "description": "Credits charged for the whole batch."
              },
              "release": {
                "type": "string",
                "enum": [
                  "v1"
                ]
              },
              "credits_remaining": {
                "type": "integer",
                "description": "Account keys: included credits left after the batch."
              },
              "credits_exhausted": {
                "type": "object",
                "description": "Present when the account ran out of credits part-way: these items weren't looked up and cost nothing.",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "upgrade_url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "rate_limited": {
                "type": "object",
                "description": "Present when the per-minute limit ran out part-way: these items weren't looked up and cost nothing.",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "retry_after": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                }
              },
              "took_ms": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  }
}
