Routing¶
Declared limits only reorder members. Exhausted, cooling or throttled
members are still tried as a last resort. A wrong guess about a quota must not
become a silent no-op. The one exception is disabled (authentication).
Two accounts on the same model¶
Two API keys on gemini-flash-latest are two daily counters, which is the
point of a pool. Two entities of one account on that model are the same
membership listed twice: they share a quota and fail together.
The pool reads each member's model and the provider config entry that owns it — the subentry first, for providers that publish several entities per account. It raises a repair only when members share a model and an account.
A 503 is this key's view
A 429 RESOURCE_EXHAUSTED / quota message is your limit. A bare 429 Too
Many Requests or a 503 UNAVAILABLE / high demand is this key being told
the model is busy. Another account on the same
model is still worth asking. The pool only skips other members of the
same account on that model for the rest of this request. A skip is
not charged as an attempt.
Providers name the model option as they please and are free to change it, so
this is a heuristic: an unreadable model is reported as null and carries no
conclusion, never a false match.
Rate limits you can actually measure¶
Providers meter three dimensions: requests per minute, input tokens per minute, and requests per day. Token counts are not available — Home Assistant returns the result, never the provider's usage report. Characters are what can honestly be measured.
calls_today and requests_today bracket the truth. A provider charges a
request when it receives it, but a server-error refusal probably costs nothing.
calls_today counts successes, requests_today counts every attempt. Routing
uses the optimistic one.
Google applies limits per project, not per API key, so two accounts really do get independent allowances. Specified rate limits are not guaranteed: a member can refuse while well inside its declared limit.
Giving up on a member¶
Each attempt has a deadline, 120 seconds by default, configurable per pool
and disabled with 0. Past it the member is abandoned and the next one is
tried. A deadline never reorders members; it only stops one from holding a
request open forever. Timeouts are their own failure kind.
Cooldowns that lengthen¶
Capacity refusals arrive in clusters, so each consecutive refusal doubles the member's cooldown, up to an hour. Only a success resets it. Cooldown is per member, not per model.
A refusal that names a spent allowance is read as quota even when it arrives as
403. Authentication is the one verdict that stops a member from being tried
again, so evidence of a quota outranks a bare status code.
Reloading the entry re-admits every disabled member. The ai_pool.reset_member
service clears cooldown, the spent-allowance mark and the disable — see
Observability.