Errors
Understand error responses, HTTP status codes, and how to handle them gracefully in your integration.
Error response format #
When the API encounters an error, it returns a consistent JSON object with three fields: a machine-readable code, a human-readable message, and the HTTP status. Your integration should always check the HTTP status code first, then use the code field for programmatic error handling.
{
"error": {
"code": "invalid_parameter",
"message": "The 'zpid' parameter must be a valid Zillow property ID.",
"status": 400
}
} | Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error identifier (e.g. invalid_parameter, rate_limit_exceeded) |
error.message | string | Human-readable explanation of the error |
error.status | integer | The HTTP status code (mirrors the response status) |
400 Bad Request #
The request was malformed or contains invalid parameters. Check your query parameters and request body before retrying.
Common causes
- Missing a required parameter (e.g.
zpidoraddress) - Invalid parameter format (non-numeric ZPID, malformed coordinates)
- Unsupported query parameter or filter value
- Request body exceeds the maximum allowed size
{
"error": {
"code": "invalid_parameter",
"message": "Parameter 'zpid' must be a positive integer.",
"status": 400
}
} 401 Unauthorized #
The request is missing valid authentication credentials. Every API request must include your API key in the Authorization header.
Common causes
- Missing
Authorizationheader entirely - Malformed header (e.g. missing the
Bearerprefix) - API key has been revoked or rotated
- Using a test key against the production environment
{
"error": {
"code": "authentication_required",
"message": "A valid API key is required. Include it as 'Authorization: Bearer <key>'.",
"status": 401
}
} 403 Forbidden #
Your API key was recognized, but it does not have permission to access the requested resource. This typically means the endpoint or feature requires a higher plan tier.
Common causes
- Accessing a Pro-only or Enterprise-only endpoint on a lower-tier plan
- Requesting bulk export features without the required subscription
- API key restricted to specific IP addresses and the request came from a different IP
{
"error": {
"code": "insufficient_permissions",
"message": "Your plan does not include access to the Markets endpoint. Upgrade to Pro or above.",
"status": 403
}
} 404 Not Found #
The requested resource does not exist. This can mean the endpoint path is wrong, or the specific property or market you requested could not be found.
Common causes
- Incorrect API endpoint path (check for typos)
- ZPID or address does not match any known property
- Property was delisted or removed from the source data
{
"error": {
"code": "not_found",
"message": "No property found for zpid '999999999'.",
"status": 404
}
} 429 Too Many Requests #
You have exceeded your rate limit. The response includes headers to help you determine when you can retry. See the Rate Limits page for your plan's allowances.
Rate limit headers
Every 429 response includes these headers so you know exactly when to retry:
| Header | Description |
|---|---|
X-RateLimit-Limit | Your plan's per-minute request limit |
X-RateLimit-Remaining | Requests remaining in the current window (will be 0) |
X-RateLimit-Reset | Unix timestamp when the current window resets |
Retry-After | Seconds to wait before retrying |
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 23 seconds.",
"status": 429
}
} 500 Internal Server Error #
Something went wrong on our side. These errors are automatically logged and our team is notified. If the issue persists, contact support and include the X-Request-Id header value from the response.
Debugging
Every response includes an X-Request-Id header with a unique identifier. When reporting issues, include this ID so our team can trace the exact request through our systems.
// Response header: X-Request-Id: req_a1b2c3d4e5f6
{
"error": {
"code": "internal_error",
"message": "An unexpected error occurred. Please try again later.",
"status": 500
}
} 503 Service Unavailable #
The API is temporarily unavailable, usually due to planned maintenance or an upstream dependency outage. These windows are typically brief. Check our status page for real-time updates.
{
"error": {
"code": "service_unavailable",
"message": "The API is temporarily unavailable. Please try again in a few minutes.",
"status": 503
}
} Error handling best practices #
Building a resilient integration means handling errors gracefully. Follow these guidelines to ensure your application stays reliable.
Retry with exponential backoff
For transient errors (429, 500, 503), retry with exponential backoff and jitter. Do not retry 400, 401, or 403 errors -- these require fixing the request or credentials.
import time, random
def fetch_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
resp = requests.get(url, headers=headers)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", 60))
time.sleep(wait + random.uniform(0, 1))
elif resp.status_code in (500, 503):
time.sleep(2 ** attempt + random.uniform(0, 1))
else:
return resp
return resp # return last response after retries exhausted Log the request ID
Always log the X-Request-Id header from error responses. This makes it dramatically faster to diagnose issues with our support team.
Handle errors by category
| Status range | Meaning | Action |
|---|---|---|
4xx | Client error | Fix the request. Do not retry 400/401/403 without changes. |
429 | Rate limited | Wait for Retry-After seconds, then retry. |
5xx | Server error | Retry with exponential backoff. Report if persistent. |