cd /news/developer-tools/every-ai-provider-fails-in-its-own-w… · home topics developer-tools article
[ARTICLE · art-53966] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

Every AI provider fails in its own way. I stopped checking status codes and built an error model instead.

A developer built an API gateway routing between OpenAI, Anthropic, and Gemini, and discovered that each provider returns errors in different formats, making naive error handling unreliable. Instead of forwarding raw status codes, the developer created a normalized error model that classifies failures into a fixed set of categories (e.g., rate_limited, overloaded) regardless of the provider. This approach eliminates provider-specific special-casing and simplifies retry logic, and has been incorporated into the developer's AI gateway project, Apiarium.

read2 min views1 publishedJul 10, 2026

I built an API gateway that routes between OpenAI, Anthropic and Gemini. I figured integrating both providers would be the hard part.

It wasn't. Calling their APIs is maybe an afternoon of work each. The hard part showed up later, the first time something went wrong.

Early on, my error handling was basically: catch whatever the provider throws, forward the status code, move on.

} catch (error) {
  res.status(error.status || 500).json({ error: error.message })
}

This worked fine until I actually looked at what each provider sends back when something goes wrong.

OpenAI wraps its errors in an object with a type

and sometimes a code

. Anthropic wraps its errors differently, with its own type

field that means something else entirely. A 429 from one provider might mean "you're sending too fast, back off." A 429 from another context might mean something closer to "we're out of capacity right now, this isn't really about your rate at all."

If you're just forwarding error.status

and error.message

straight through, none of that nuance survives. Your own error handling ends up being provider-specific whether you meant it to be or not, because the shape of the failure is different depending on who you called.

Instead of trusting each provider's raw error shape, every call now normalizes into the same internal error model before it reaches the response:

} catch (error) {
  const classified = classifyProviderError(error)

  res.status(classified.httpStatus).json({
    error: 'AI provider error. Please try again.',
    error_class: classified.error_class,
    provider: classified.provider
  })
}

error_class

is one of a small fixed set: rate_limited

, overloaded

, quota_exceeded

, invalid_request

, authentication_error

, server_error

. That's true regardless of which provider actually failed. The raw provider error still gets logged for me to debug, but what the caller sees is the category of failure, not the provider's specific wire format.

The point isn't that this retries anything automatically. It doesn't. It's that "should I retry, and how" becomes a decision you can make once, based on error_class

, instead of once per provider you happen to be calling that day.

I expected the differences between providers to be about capability, better reasoning, different context windows, that kind of thing. What actually ate my time was smaller and dumber: two APIs disagreeing about what a 429 even means.

Once you stop trusting the HTTP status code to tell you the whole story and start asking "what actually happened here, independent of who I called," a lot of the provider-specific special-casing just goes away.

If you're building anything that calls more than one LLM provider, worth checking early: are you branching your error handling on the provider, or on the actual failure type? Those aren't the same question, and the difference only shows up once something breaks in production.

This eventually became part of Apiarium, the AI gateway I'm building.

Not because I wanted another abstraction layer.

Because I got tired of writing provider-specific error handling.

── more in #developer-tools 4 stories · sorted by recency
── more on @openai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/every-ai-provider-fa…] indexed:0 read:2min 2026-07-10 ·