Skip to main content
Every provider has its own SDK, its own parameter names and its own response shape. “How many results” is 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:
Nothing is hidden to get there. Unmapped provider fields stay in 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:
Nothing is thrown away. Unmapped provider fields land in 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.