More from the blog

Try it yourself — free credit, no card.

Start free →
What we tell users when 9 of our 13 models are down — FlintAPI Blog
FlintAPI
Models Pricing Playground Consulting Chat API Docs Sign In Start Free
[llm operations reliability api]

What we tell users when 9 of our 13 models are down

2026-10-284 min read

A gateway fails in two ways. The loud way is a 500. The quiet way is a model that

has been returning `503` for six hours while every dashboard says "active".

We shipped the quiet way twice this month. This is what we changed, and the

contract you can now check before you build against us.

The mistake

We had a health checker that did this, more or less:

for model in catalogue:
    if upstream_returns_2xx():   mark_healthy(model)
    else:                        mark_unhealthy(model)
        if model.unhealthy_for_2_checks(): deactivate(model)

Two assumptions in four lines, both wrong.

**One: "unhealthy" is not always the model's fault.** When the upstream account

runs out of balance, *every* model behind it fails at the same instant, with the

same error. My checker saw eight failures and dutifully deactivated eight models.

The models were fine. The account was empty. Deactivating them made the outage

longer, because now they would not even be retried.

The fix was a distinction the code did not previously have:

if all_checks_failed_with_account_level_error():
    log("SKIP auto-isolate: account-level failure (balance) -- do NOT deactivate")
else:
    isolate_the_single_bad_model()

Now that line exists in the log, and it looks like this:

[auto-isolate] SKIP kimi-k3: account-level failure (balance) -- do NOT deactivate the model

**Two: "2xx from upstream" does not mean "works for this model name".** Some

suppliers route by model name and return a perfectly valid 200 with an error body,

or accept the name in `/models` and reject it in `/chat/completions`. A health

check that only looks at status codes will mark those models healthy forever.

The contract we publish now

If a model is unavailable, the response is not a mystery. It is this, with

`HTTP 503`:

{
  "error": {
    "message": "Service temporarily unavailable: upstream model capacity is exhausted for the requested model. Please retry later or contact support@flintapi.ai.",
    "type": "service_unavailable",
    "code": "upstream_capacity_exhausted",
    "hint": "Other models may still be available right now: https://flintapi.ai/api/health/models",
    "available_models": ["deepseek-v4-pro", "deepseek-v4.1-flash", "glm-5.3", "glm-5.3-flash"]
  }
}

Three properties matter here:

1. **A machine-readable code** (`upstream_capacity_exhausted`), so your retry and

failover logic does not have to parse English.

2. **The list of models that are working right now**, so a 503 is actionable

— you can fall back without a human.

3. **`Retry-After`**, so a naive client does not hammer us in a loop.

And the single source of truth, which is public and needs no auth:

GET https://flintapi.ai/api/health/models

It returns `total` and `healthy` counts joined against the catalogue that we

actually serve. If that endpoint says four models are healthy, then four models

are healthy — we do not keep a second, prettier number anywhere else.

Why we do not "just multiply the model list"

It is tempting to advertise thirty models and route whatever works. That converts

our availability problem into your correctness problem: your temperature, your

token budget, your structured-output assumptions silently run on a different model

than the one you named. We would rather return a loud 503 for a named model than a

quiet 200 from a substitute.

If you *do* want substitution, it is opt-in: send `model: "flint"` and the router

decomposes the prompt and picks per sub-task, and it says which model answered.

The numbers, dated

As of 2026-10-28: **13 models in the catalogue, 4 healthy**, because one upstream

account is at a negative balance of about three cents. This post exists partly

because that is an embarrassing ratio and partly because the interesting part is

not the ratio — it is that the ratio is *published, checkable, and per-model*

rather than hidden behind a green dashboard.

If you are running something similar, two checks are worth adding today:

- Distinguish **account-level** failures from **model-level** failures before any

automated isolation runs. Otherwise one billing problem takes out your whole

catalogue and looks like thirteen separate bugs.

- Return the **currently-available list** inside your error body. It costs one

join and turns every 503 into a self-healing instruction.

*Related: [What Chinese LLM APIs actually cost in 2026](/blog/chinese-llm-api-pricing-2026) and

[Why your health check said "up" while the model was gone](/blog/health-check-said-up-model-was-gone).*