max_results on Tavily, count on Brave and
You.com, numResults on Exa. The page text is content on Tavily, text on
Exa, snippet on Perplexity, and nested under contents on You.com. Swapping
backends means rewriting the call, the options and the parsing.
WebRouter normalizes all three:
SearchResult.extra / ExtractedDocument.extra, the untouched payloads stay in
raw, and an option a backend cannot honour raises instead of being quietly
dropped, so a call never runs under weaker constraints than you asked for.
The pieces
Four rules keep the abstraction honest:
Unset stays unset. Fields you leave alone are not sent, so each backend
applies its own defaults rather than inheriting ours. (Three deliberate
exceptions: an extract sends a format even when you named none,
markdown
wherever the backend can return it, so the same call answers alike whichever
backend serves it, and document.format is true because it was asked for, not
because a vendor default has not moved. A
browser session opened with no timeout gets ten minutes rather than the
vendor’s own limit, because it is billed until something ends it. And local
is headless unless you say otherwise, because there is no vendor default to
keep and a window would open on your screen.)
Nothing is silently dropped. If a provider does not map an option you set,
or cannot return the format you asked for, it says so instead of quietly
doing something weaker:
SearchResult.extra
or ExtractedDocument.extra, and the complete payloads in raw, so
normalization is never lossy.
Nothing fails quietly. An extract over several URLs reports the ones that
did not come back in failures and returns the rest; a call that produced no
document at all raises. An empty answer is never something you have to go
looking for.