Skip to content
Journal / education

Normalized APIs vs provider lock-in: what one schema really costs

Normalized tool APIs trade provider quirks for portability. What the lowest-common-denominator trap costs you and how include_raw gets it back.

Every tool provider invents its own API. Different auth, different parameter names, different response shapes, different error formats. Integrate three scraping APIs and you have written three clients, three retry handlers, and three response parsers that all produce slightly different notions of “the page content”. This is the integration tax, and it is the quiet reason most agents ship with exactly one provider per tool: not because one is enough, but because two is twice the work.

Normalization is the standard answer: define one schema per category, translate every provider into it, and let callers write against the schema instead of the vendor. It is what we do at route.tools across nine categories, it is what every gateway-shaped product does, and it is also, done carelessly, a way to lose things you paid for. I want to be honest about both halves.

What normalization buys you

Swappability. If every search provider returns the same shape, changing providers is a config change instead of a code change. On our router that is literally a saved routing preference: flip the default and no application code moves.

Failover becomes possible at all. This is the part people miss. Automatic failover requires normalization; it is not an optional nicety on top. If provider A’s response shape differs from provider B’s, then failing over from A to B silently hands your application a differently-shaped object mid-incident, which is a bug generator, not a resilience feature. One schema is the precondition for the router trying up to 3 providers behind a single request.

One mental model. Your team learns one search API, one scrape API, one transcription API. Provider docs become an implementation detail.

What normalization costs you: the LCD trap

A common schema can only comfortably promise what every provider in the category can deliver. Features that only one vendor offers either get awkward optional fields or get dropped. This is the lowest-common-denominator trap, and it is not hypothetical; I can point at it in our own catalog.

Take text-to-speech. ElevenLabs supports multiple output formats natively, but through our normalized speech endpoint, ElevenLabs is mp3 only. That is a real capability, present on the provider, narrowed by the abstraction. Or document parsing: LlamaParse has a premium mode that costs about 15x the base units and behaves practically like a different product tier. A schema flat enough to cover LlamaParse, Reducto, and Mistral OCR uniformly has to work to expose that kind of provider-specific depth, and every gateway product handles it imperfectly, ours included.

The general rule: normalization is cheapest where products are most alike, and most expensive where a vendor’s differentiation is the product. Same-model hosting (three vendors serving identical FLUX weights) normalizes almost losslessly. Neural search versus SERP search normalizes with visible seams.

The escape hatch: include_raw

The design answer we landed on is refusing to make the abstraction mandatory. Pass include_raw: true on any request through our router and the response carries the provider’s original, untranslated payload alongside the normalized fields.

This changes the deal you are making. Without an escape hatch, normalization is a bet that you will never need anything the schema omits, which is a bet you will eventually lose. With one, the normalized schema becomes the default you use 95% of the time, and the raw payload is there for the cases where a provider returns something valuable that the schema flattens: extra metadata from a search result, layout detail from a parser, whatever the vendor exposes that the category schema does not. You keep swappability for the common path and full fidelity for the exceptional one.

It also makes migrations honest. Before committing to a provider, run your traffic with raw payloads on and inspect what each vendor actually returns, rather than trusting either the normalized view or the marketing page.

So is lock-in ever the right call?

Sometimes, yes. If your product is built around one vendor’s genuinely unique capability, coding to their native API is coherent: you have decided the differentiation is worth the coupling. The mistake is making that decision by default, for every category, because integrating one provider was easier than abstracting over three. Then you have taken on lock-in’s costs (no failover, no price leverage, a rewrite standing between you and every alternative) without its benefit.

My rule of thumb after routing across roughly 30 providers: normalize the categories where you buy a commodity (search, scraping, transcription, embedding at the API level), and think hard before normalizing away the thing you specifically chose a vendor for. And whatever abstraction you adopt, yours or a router’s, demand an escape hatch. An abstraction you cannot see through is just lock-in to the abstraction instead of the vendor, and I laid out what routers should owe you in what is a tool router.

If you want to see what a normalized category looks like in practice, the scraping comparison page shows three providers behind one schema, prices and quality scores side by side.