# Thirteen providers, thirteen ways to fail

Note by Sumaia Aktar · https://sumaia.xyz · 2026-06-10
> What thirty-seven merged pull requests into laravel/ai taught me about the difference between an integration that demos and one that ships.


Every LLM provider has an API that works beautifully in the README. The differences show up on the days you would rather not think about: the day the credit card expires, the day a model is renamed, the day a stream dies at token 400 of 2,000.

I have spent a year on those days, inside Laravel's official AI SDK, and the pattern I keep seeing is this: providers agree on the happy path and disagree about everything else.

**Error shapes.** OpenAI puts the reason in `error.code`. Another provider puts it in the message. A third returns a 200 with an error object inside the body. If you want failover, meaning "try the next provider when this one fails", you first need a classification step that turns thirteen dialects into one question: *is this worth retrying somewhere else?* That is what `HandlesFailoverErrors` does, and adding it to the Cohere, Jina and Voyage AI gateways was two of the smaller pull requests with the largest effect.

**Money.** Running out of credit is the one failure an application can actually act on: alert somebody, switch providers, stop the scheduled job. OpenRouter and DeepSeek both report it as an ordinary client error, indistinguishable from a bad request. A typed `InsufficientCreditsException` is not sophisticated engineering. It is the difference between an application that pages you and one that quietly returns empty results for a weekend.

**Usage.** Token accounting is how you bill customers and how you notice a runaway prompt. Several OpenAI-shaped providers return usage in a slightly different place, and the SDK was dropping it. When usage is missing, nothing crashes; the invoice is simply wrong. Those are the bugs I have learned to go looking for.

**Streams.** A broadcast agent that fails mid-stream used to leave the browser waiting. Now it broadcasts `stream_failed`. The rule: if a process can die, the last thing it does should be to say so.

The common thread is that none of these were features. They were the parts of the SDK that only matter in production, which is why they were still missing. If you want to contribute to an open-source project and do not know where to start, start with the day everything goes wrong.
