X-Search-Status
"No flights" and "the search failed" are different answers
Everywhere else, 200 [] is ambiguous. Here an empty array is only ever reported when the page it came from positively said so.
X-Search-Statuson every response: ok · empty · partial · degraded- Unreadable pages are retried automatically. Degraded means retry, empty means believe it
- Opt-in
strict: trueturns a degraded search into an HTTP 503
Free tier on RapidAPI. No card to try.
HTTP/2 200
x-search-status: degraded
x-search-reason: blocked_page
x-search-retries: 1
x-search-attempts: 3
x-search-combinations: 1
x-search-fallback: exhausted
x-search-lost-combinations: 1
x-search-results: 0
x-search-unreadable-pages: 2
[] ← says NOTHING about availability. Retry it.HTTP/2 200
x-search-status: ok
x-search-reason: blocked_page
x-search-retries: 1
x-search-attempts: 3
x-search-combinations: 1
x-search-fallback: used
x-search-fallback-recovered: 1
x-search-results: 5
x-search-unreadable-pages: 2
[ { "total_price": "$823", … } ] ← 5 real itinerariesThe problem
Every scraper gets handed pages it cannot read
A consent wall, a bot check, a truncated response. Most flight APIs return an empty list anyway, and your product tells a user something false.
This API separates the two. A search that fails to read a page is retried automatically, and an empty array is only ever reported as a real answer when the page it came from positively said so, including the case where flight rows were on the page but their prices could not be read, which is what a Google markup change looks like from the inside. A page where Google genuinely reports no flights is never retried, so a real empty result costs you nothing extra. Whatever is left is reported on the response.
The two captures above are that story happening for real: the first request hit a blocked page and said so (degraded, blocked_page) instead of pretending [] meant no flights, and the retry seconds later came back ok with 5 itineraries and x-search-retries: 1 on its record.
The contract
The four states of X-Search-Status
| X-Search-Status | What it means |
|---|---|
ok | Results returned, array complete. |
empty | The search completed and Google genuinely has no itineraries for that route and date. The empty array is the answer, not a failure you should retry. |
partial | There are itineraries, but the array is knowingly short: rows whose price could not be read were dropped, or a round-trip’s return-leg fan-out lost some of the outbound candidates it set out to price. Real results, minus the ones the search could not deliver. |
degraded | The search did not complete. The empty array says nothing about availability. Retry it. |
Round-trip gets the same treatment as one-way, which is harder than it sounds: a round-trip prices a return leg for every outbound candidate, and each of those fetches can fail on its own. empty is only reported when every candidate was attempted and every one of them read a real Google Flights page saying it had nothing. A fan-out that was blocked, or that stopped on the request's time ceiling, reports degraded or partial, never "no flights".
Diagnostics
The reason, and the work the search did
Status is the field you branch on. The rest of the x-search-* family tells you why and how much.
X-Search-ReasonstringThe cause when there is one: blocked_page, unrecognized_page, unreadable_prices, upstream_timeout, search_truncated, upstream_status_<code> and a few more. It records the first failure, so it can ride along on a response a retry already rescued. branch on X-Search-Status, log the reason.
X-Search-Results / X-Search-Attempts / X-Search-CombinationsintHow much work the search did: results returned, page fetches attempted, and date-pair combinations the search set out to price.
X-Search-Retriesint, when non-zeroAutomatic retries that rescued unreadable pages. The captured "ok" response above carries x-search-retries: 1.
X-Search-Lost-Combinations / X-Search-Incomplete-Combinationsint, when non-zeroRound-trip fan-out accounting: outbound candidates whose return-leg pricing was lost or cut short. The reason a short array can honestly call itself partial.
X-Search-Unreadable-Pagesint, when non-zeroPages fetched but not parseable, the raw material of a degraded verdict.
The captures on this page include a few further internal counters. All x-search-* headers are additive and safe to ignore. The only one your code should branch on is x-search-status.
In code
Branch on the status, not the array length
The whole integration is one if-statement before you touch the body.
r = requests.post(url, headers=headers, json=body)
if r.headers.get("X-Search-Status") == "degraded":
# The search did not happen.
# Do NOT tell the user "no flights found".
raise RuntimeError(
f"search incomplete "
f"({r.headers.get('X-Search-Reason')}), retry")
flights = r.json()
if not flights:
# Status is "empty" - Google really has
# nothing for this route and date.
print("No flights on this route for these dates")Prefer an error to an empty list?
Send "strict": true and a degraded search returns HTTP 503 instead of a misleading []:
HTTP/2 503
{
"error": {
"type": "search_incomplete",
"reason": "blocked_page"
}
}strict is opt-in and off by default. Leave it out and you get exactly the responses you get today. The body of a normal response is unchanged and the headers are additive, so nothing you have already built breaks. Full walkthrough: handling empty flight-search results.
Pricing
The headers ride on every plan
| Plan | Price / mo | Requests | $ / 1k req | Overage | Rate limit | |
|---|---|---|---|---|---|---|
| BASIC | Free | 10 / mo | — | hard cap | — | Get this plan → |
| PRO | $10 | 2,500 / mo | $4.00 | $0.003 / req | 150 / min | Get this plan → |
| ULTRArecommended | $25 | 10,000 / mo | $2.50 | $0.003 / req | 250 / min | Get this plan → |
| MEGA | $50 | 50,000 / mo | $1.00 | $0.001 / req | 500 / min | Get this plan → |
Every plan includes every endpoint. You only choose volume and rate limit. Read from the live listing on 2026-08-26; the listing is authoritative.
Questions, answered plainly
- Why does a flight API return an empty array?
- For two very different reasons: either there really are no flights on that route and date, or the scrape behind the search silently failed: a consent wall, a bot check, a truncated page. Most APIs return [] either way and you cannot tell which happened. Here the X-Search-Status header says which: "empty" is a real answer, "degraded" is a failed search.
- What does X-Search-Status: degraded mean?
- The search did not complete. The page could not be read even after automatic retries. The empty (or short) array says nothing about availability. Retry the request; do not tell your user "no flights found".
- What does X-Search-Status: partial mean?
- Real results, knowingly incomplete: rows whose price could not be read were dropped, or a round-trip’s return-leg fan-out lost some outbound candidates. What you got is trustworthy; there may have been more.
- When should I retry a search?
- Retry on "degraded", that is its meaning. Never on "empty": an empty result is only reported when the page positively said there are no flights, and Google reporting no flights is never retried internally either, so a real empty costs no extra time.
- Can I get an error instead of a misleading empty list?
- Yes. Send "strict": true and a degraded search returns HTTP 503 with {"error": {"type": "search_incomplete", "reason": ...}} instead of []. It is opt-in and off by default; leave it out and responses are exactly what they are today.
- X-Search-Reason is set but the status is ok. Did something fail?
- Nothing you need to act on. X-Search-Reason records the first failure the search hit, so it can ride along on a response that a retry already rescued. The captured "ok" response on this page carries reason blocked_page for exactly that reason. Branch on X-Search-Status, read X-Search-Reason for diagnostics.
- Do these headers change the response body?
- No. The body of a normal response is unchanged and the headers are additive, so nothing already built against the API breaks. strict is the only behavior change, and it is opt-in.
Build on answers, not ambiguity
Live Google Flights data that says what happened (ok, empty, partial, or degraded) on every single response.
Free tier: 10 requests/month. No card to try.