Notes ·
An empty result is not an error
An agent asks a search API a question and gets back an empty list. What happened?
Either the web genuinely had nothing, or something broke on the way. Those two facts ask the agent to do completely opposite things — reword the question, or wait and retry — and an empty array on its own cannot tell them apart.
Most integrations guess. That is the bug.
What goes wrong when the difference is lost
An agent handed an empty list for a systems failure learns the wrong lesson. It concludes the web has nothing on the subject, reports that confidently, and moves on. The failure is invisible: no exception, no retry, no alert. A search API that was down for ten minutes produces ten minutes of confidently wrong answers rather than ten minutes of errors.
The reverse is worse in a quieter way. An agent that retries every empty result burns quota re-asking a question that genuinely has no answer, and takes a rate limit for it.
The status code has to carry it
The fix is not a field in the body. It is the status code, because that is the one thing every HTTP client already branches on, including the ones you did not write.
- 200 with results. Normal.
- 200 with an empty list. The search ran and found nothing. Reword it. Retrying will not help.
- 429. Rate limited or out of quota, with
Retry-After. Wait exactly that long. - 502. The search could not be run. Not your fault, not an empty web. Retry shortly.
Those four are the whole contract, and they are written down rather than implied. Once they are distinct, an empty list is safe to treat as an answer, which is what makes it useful. The guarantee is not “you will always get results”. It is “an empty list always means what it says”.
The harder case: a source that answers the wrong question
The interesting failure is not an outage. It is a source that returns HTTP 200, with well-formed markup, and results about something else entirely.
The usual shape is a degraded search: the upstream quietly drops most of a long query and
answers one common word of it. A search for best practices for postgres connection pooling in node comes back as consumer electronics and dictionary entries for “best”. It
parses perfectly. Every mechanical signal says success.
That has to be caught on content, not on status, because there is no status to catch it by. The test we use is how many distinct query terms the best result on the page covers, scaled to the length of the query — two terms out of three is an answer, two out of five is a coincidence. A source that fails it is reported as failed rather than passed through as a thin result.
What to ask of any search API you integrate
- Does an empty list ever mean something other than “nothing matched”?
- Is a total failure distinguishable from an empty result without parsing the body?
- Does a capability the service does not have return an error naming it, or a 200 quietly missing the thing you asked for?
That last one is the most important and the least often asked. A response that looks successful while omitting what you requested sends you debugging the wrong layer, some time next week.