Core concepts
Errors & the failed-calls-free rule
A locked error-type catalog mapped 1:1 to HTTP status, a complete machine-stable sub-code list, and one guarantee: failed, empty, blocked and not-found calls are never billed.
Failed / bad-data calls are NEVER billed
On every error response,
error.creditsCharged is 0. In a fan-out, every failed platform contributes 0. The only credit-bearing outcomes are a successful synchronous result and a successfully completed async job. This is a load-bearing product guarantee — and why our reliability claim is true at the billing layer.The error object
Branch on type (the error class, mapped to HTTP status), then on code (a stable, finer-grained reason). Every response also carries a requestId, retryable flag, and a docUrl that links to the matching page below.
Error types
| HTTP | type | When it fires | Retryable |
|---|---|---|---|
| 400 | invalid_request | Malformed, missing or contradictory input you control. | No — fix the request |
| 401 | authentication_error | Missing / malformed Authorization, unknown or revoked key. | No — fix credentials |
| 402 | insufficient_credits | The plan / balance lacks the credits this call would cost. | No — until topped up |
| 403 | permission_denied | Authenticated but not allowed: a missing scope, an unverified email, or a disabled account. | No — change plan/scope |
| 404 | not_found | The addressed resource does not exist (job, listing, route). | No |
| 429 | rate_limited | The per-plan token-bucket rate limit was exceeded. | Yes — after Retry-After |
| 500 | internal_error | An unexpected StayingAPI-side fault. | Maybe — retry with backoff |
| 503 | upstream_unavailable | Every upstream source for the request failed or was blocked. | Yes — with backoff |
| 504 | upstream_timeout | Synchronous upstream work exceeded the request deadline. | Yes — with backoff |
Sub-code catalog
The complete error.code set. New codes may be added over time (additive) — branch on them for precise handling but tolerate unknown ones. Each links to its own page (the value of error.docUrl).
| code | type | Trigger |
|---|---|---|
| missing_parameter | invalid_request | A required parameter is absent (e.g. checkOut given checkIn). |
| invalid_parameter | invalid_request | A parameter is present but malformed (bad type, format or enum value). |
| invalid_date_range | invalid_request | checkOut / endDate is not strictly after checkIn / startDate. |
| date_in_past | invalid_request | A supplied date (checkIn / startDate) is in the past (evaluated in UTC). |
| child_ages_mismatch | invalid_request | childAges[] length does not equal children. |
| stay_too_long | invalid_request | The booking window exceeds the per-endpoint maximum stay. |
| window_too_long | invalid_request | The query date window exceeds the per-endpoint cap (availability > 365 days). |
| invalid_listing_url | invalid_request | The supplied url is not a recognisable listing URL for the platform. |
| invalid_property_type | invalid_request | A propertyType[] value is not in the enum. |
| invalid_amenity | invalid_request | An amenities[] token is not in the canonical taxonomy. |
| invalid_price_range | invalid_request | priceMin / priceMax is invalid (negative, or priceMax < priceMin). |
| invalid_language | invalid_request | A language filter value is not a valid ISO-639-1 code. |
| invalid_sort | invalid_request | A sort value is not in the endpoint's allowed enum. |
| invalid_cursor | invalid_request | The pagination cursor is malformed or not base64-decodable. |
| limit_out_of_range | invalid_request | limit is < 1 or > 100. |
| mutually_exclusive_params | invalid_request | Conflicting or ambiguous identifiers were supplied (e.g. both listingId and url, or none). |
| platform_not_enabled | invalid_request | A requested platform is not enabled for the request (fast-follow, or not on the plan). |
| no_enabled_platform | invalid_request | After resolving platforms[], no platform remains to query. |
| needs_country | invalid_request | A bare booking.com slug was passed to /v1/listing without a country. Booking slugs are NOT globally unique — the same slug exists per country — so the target cannot be safely resolved. |
| missing_api_key | authentication_error | No Authorization header. |
| invalid_api_key | authentication_error | The Authorization header is present but the key is unknown or malformed. |
| revoked_api_key | authentication_error | The key was revoked or soft-deleted. |
| scope_insufficient | permission_denied | The key / token lacks a scope required for the operation. |
| email_unverified | permission_denied | The API key is valid, but the account email has not been verified yet — so LIVE credit (calls made with a stay_live_ key) is locked until you confirm your email (§7.5.1). Your stay_test_ sandbox key is unaffected and keeps returning deterministic fixtures at zero cost. |
| subscription_required | permission_denied | The account tried to buy a top-up (POST /v1/billing/topups) without an active paid subscription. Top-up credits are a SUBSCRIBER add-on — a free / unsubscribed account cannot purchase them. |
| free_tier_budget_exhausted | permission_denied | Your free-plan account has reached its cumulative free-tier upstream-spend budget. We declined to spend further on live calls — no upstream source was contacted and no credits were charged. |
| listing_not_found | not_found | The addressed listing / property does not exist upstream. |
| job_not_found | not_found | Unknown or expired jobId. |
| identity_mismatch | not_found | The resolved listing's canonical identity (booking country/slug, or vrbo numeric id) did not match what was requested — StayingAPI refuses to return a different (wrong) property rather than serve plausible-looking wrong data. |
| no_availability | not_found | The listing exists, but the requested STAY is not bookable — at least one night between checkIn and checkOut is already taken, so the platform quotes no price for that range. This is a true answer about the property’s calendar, not an upstream failure. |
| price_not_found | not_found | The platform did not quote this listing for the requested dates and party, and did not say why. On Vrbo this means the listing was not in Vrbo’s own priced search for that stay: most often the stay is shorter than the listing’s minimum, a night is taken, or the party is too large. |
| credit_balance_too_low | insufficient_credits | The balance is below the call's cost. |
| rate_limit_exceeded | rate_limited | The token bucket is exhausted (per-key for REST, per-user for MCP). |
| all_actors_failed | upstream_unavailable | Every primary + fallback source for the request failed. |
| actor_blocked | upstream_unavailable | Upstream anti-bot / block detected with no usable rows. |
| upstream_degraded | upstream_unavailable | Every upstream source for the request failed or was blocked. |
| actor_timeout | upstream_timeout | Synchronous upstream source work timed out without producing data. |
| internal_error | internal_error | An unhandled StayingAPI-side fault. |