Failover and uptime
Failover and uptime
A model on this gateway is not one connection to one provider. It is a pool: several
accounts behind the same model id, and a request that fails on one is retried on another
inside the same call.
You do not configure this. It is what the pool is for, and knowing how it behaves tells you
when an error is worth reporting.
What a request goes through
- Pool selection. The model id is resolved to the accounts that can serve it.
- Attempt. The request goes to one account.
- Retry on a retryable failure.
429, a5xx, or a transport failure moves to the
next account. - Escalating passes. If the whole pool is busy or cooling down, the gateway waits and
tries again, roughly twelve seconds of tolerance in total. - Give up. Only then do you see
429 pool_exhaustedor503 model_unavailable.
A request-shaped failure stops instead of retrying. A 400 will fail identically on the
next account, so retrying it would turn one malformed request into several failed attempts
and make a provider look broken.
What you see, and what it means
| You see | Meaning |
|---|---|
200 |
normal, possibly after internal retries you never noticed |
429 rate_limited |
one upstream account is throttled, wait carries Retry-After |
429 pool_exhausted |
every account for that model is throttled at once |
502 upstream_error |
the provider failed in a way retries did not fix |
503 model_unavailable |
nothing healthy left to try, or an operator-side credential problem |
504 request_timeout |
the request ran past its time limit |
A provider flap writes many rows in telemetry and few errors at the client, because the
retry ladder absorbs them. When the operator reports a spike, the number that matters is
how many failed after a retry, not how many attempts failed. That distinction is why “the
uptime dropped” and “I got errors” are different measurements.
Reading Retry-After
Every 429 carries it, and every failure carries x-should-retry. When Retry-After
says 6 seconds, the pool has an account free in 6 seconds. Waiting is nearly always better
than switching models, because switching costs you a model choice for a wait you could
have sat through.
Health is per account, not per model
An account that fails repeatedly is benched briefly so a burst of traffic does not keep
landing on a broken connection. Traffic then goes to its siblings. This is invisible to
you except as a 200 that took slightly longer.
An account that is genuinely dead stops receiving traffic until an operator fixes it. That
is when a model may vanish from GET /models rather than stay listed and fail: an
out-of-stock model is worse than an absent one, because a buyer who paid for it needs a
refund.
When to report a problem
Report with the request_id when:
- you get the same
coderepeatedly for several minutes, not one blip - one model fails while another works, and both are in the catalogue
- a
5xxrepeats on a request that has worked before
Do not report a single 429 or a single 502: read Retry-After, retry, and only report
it if the retry also fails.
Measuring it yourself
ai_supplier_logs is operator telemetry, but your own view is the ledger: a failed call
costs nothing. If a batch of requests failed, GET /transactions will not show charges for
them, which is the fastest way to confirm you were not billed for an error.
curl -s "https://hisoka.ai/api/v1/transactions?limit=5&type=usage" \
-H "Authorization: Bearer $HISOKA_API_KEY" | jq '.data[] | {model, amount_micro_usd, created_at}'
A gap where your failed requests would be is the confirmation that a failure at the gateway
is not a charge against your balance.