Blog · September 10, 2026

A hardcoded model name is a single point of failure.

Somewhere in your codebase is a string literal — "gemini-2.5-flash", "gpt-4o-mini" — and every AI feature you've shipped depends on it. That string is a production dependency with an uptime you don't control, a price you don't set, and a retirement date you didn't pick. Most apps treat it as a constant. It isn't.

One string, four different failures

A database connection fails in basically one way: it's unreachable. A model name fails in four, and each one deserves a different response from your code.

Rate limits are the everyday failure. Providers cap requests per minute and per day, and on several APIs — Google's Gemini free tier among them — the caps are tracked per model, not per account. A small spike of users at the wrong minute and every request comes back 429, even though your account is fine and other models sit idle.

Capacity errors are the provider's bad day becoming yours: a 500, or a 503 with some variant of "the model is overloaded." These are transient in the truest sense — the same request often succeeds seconds later, on the same model, changed in no way at all.

Retirement is the one that ambushes mobile developers. Every major provider — OpenAI, Anthropic, Google — versions its models and retires old ones on published deprecation schedules. The string that has worked since launch starts returning "model not found," and unlike a rate limit it doesn't recover, doesn't warn at runtime, and hits every install on the same day.

Account problems — an expired card, a key you rotated after a leak, a suspended project — come back as 401s and 403s. No amount of retrying fixes these, and no fallback model will accept the same broken credentials.

In a shipped binary, that string is unpatchable

On the web, a bad model name is a bad deploy: fix the string, push, done in minutes. In a mobile app it's a release. The fix has to be written, built, submitted, reviewed, rolled out — and then adopted by users, many of whom won't update for weeks. Between the provider's deprecation date and your fix reaching the last user, your AI feature is a button that throws. Whatever else you take from this post: the model name must not require a release to change.

The fix is a list, not a string

The pattern that survives all four failures is small: replace the string with an ordered list of model names, kept in exactly one file, imported by everything that makes an AI call. Element zero is the model you actually want — the newest, cheapest one that clears your quality bar. The rest are understudies, ordered by preference.

ShotCanvas has six services that call Gemini — headline writing, store metadata, listing translation, screenshot vision, layout generation — and all six read the same list from the same file. No service anywhere types a model name of its own. When a new Flash version ships, one line changes and every feature moves at once; when something breaks at 2am, there is exactly one place to look.

Classify the error before you retry

The loop that walks the list has one job beyond iteration: deciding which failures deserve which response. Getting this wrong wastes the pattern — retrying a 403 forever, or abandoning a model over a blip that would have cleared in two seconds.

Retry the same model for 429s and 5xxs: wait, then try again, doubling the wait each time, with a little randomness added so a thousand clients don't all come back in the same instant. If the response names a wait time (a Retry-After header or its JSON equivalent), honor it. Cap it at two or three attempts — you're absorbing a blip, not laying siege.

Move to the next model when retries run out, or immediately on "model not found." Per-model rate limits are exactly why this works: the quota you just exhausted says nothing about the next model's quota. A retired flagship says nothing about its successor.

Stop entirely on 401 and 403. These are account problems, they will fail identically down the whole list, and burning through your fallbacks just delays the real fix. Surface the provider's actual message to whoever can act on it — not a generic "something went wrong."

Keep the list where you can edit it

A list compiled into the binary still needs a release to change, which un-solves the retirement problem. So the compiled-in list is the cold-start default, and the live list is fetched at launch from server-side config — Firebase Remote Config, a public-read Firestore doc, a JSON file behind your CDN, anything you can edit in under a minute. The day a provider deprecates your primary, you reorder a config doc and every user is on the new model at next launch. No build, no review, no waiting for updates to trickle out.

Log which model actually answered

A fallback chain that works too well creates its own failure mode: silent degradation. Your primary starts failing, the second model absorbs the traffic, users notice nothing — and neither do you, until the fallback also fails and you're debugging two outages with no history. The fix costs one field: tag every AI request's log line or analytics event with the model that served it. The day the mix shifts, a chart moves. You want to find out from the chart.

The whole pattern: one ordered list in one file · retry 429/5xx with backoff and jitter, honoring Retry-After · fall through on "model not found" or exhausted retries · stop dead on 401/403 and show the real error · fetch the list from server config at boot · log the serving model. About forty lines, no framework required.

This is table-stakes plumbing now

None of this is exotic engineering — it's the AI-era equivalent of not hardcoding your database host. Every AI feature in ShotCanvas — the headlines it writes, the store metadata it generates, the screenshot layouts it composes — runs through exactly this chain, which is why a provider's bad afternoon has never taken the feature down. Your users will never see the list, the backoff, or the log field. They'll just notice the AI button always works.

Let AI design your screenshots When the chain runs dry: honest AI errors