Fail2Ban
Fail2Ban protects the rest of a credential pool from one upstream that has gone bad. When a specific credential + model pair fails repeatedly with a configured HTTP status, the router stops sending that pair traffic for a configurable duration — round-robin and fallback selection simply skip it, the same way they skip a rate-limited or manually disabled credential.
fail2ban:
max_attempts: 3
ban_duration: permanent
error_codes: [401, 403, 429, 500, 502, 503, 504]
# error_code_rules:
# - code: 429
# max_attempts: 5
# ban_duration: 5m
Parameters
| Parameter | Type | Description |
|---|---|---|
max_attempts |
int | Failed attempts (at the configured codes) before a credential+model pair is banned |
ban_duration |
string | Ban duration (permanent, or a duration like 5m, 1h) |
error_codes |
[]int | HTTP status codes that count toward the ban threshold |
error_code_rules |
[]rule | Per-error-code override of max_attempts/ban_duration (see below) |
credential_overrides |
map | Per-credential override of error_codes/error_code_rules (see below) |
A response outside error_codes is never counted, no matter how often it repeats — Fail2Ban only reacts to codes it's told to watch. A 2xx response always resets the failure counter for that pair, regardless of error_codes.
Per-Error-Code Rules
Override max_attempts and ban_duration for specific error codes, globally:
fail2ban:
max_attempts: 3
ban_duration: permanent
error_codes: [401, 403, 429, 500, 502, 503, 504]
error_code_rules:
- code: 429 # Rate limit errors
max_attempts: 5
ban_duration: 5m
A code without its own rule falls back to the top-level max_attempts/ban_duration.
Per-Credential Overrides
"What counts as a failure" is not universal across upstreams. A credential that goes straight to the real provider signals a rate limit or outage with 429 or 5xx, and the defaults above catch that. A credential that sits behind a reseller or aggregator can signal an entirely different condition — e.g. a blocked account — with a plain 400 and a generic error body. 400 is not in the default error_codes on purpose: it's also the code an ordinary malformed client request gets, and banning on it globally would ban unrelated credential+model pairs on the first bad request from any client.
credential_overrides lets you widen (or otherwise change) error_codes/error_code_rules for one specific credential without touching the global settings that apply to everyone else:
fail2ban:
max_attempts: 3
ban_duration: permanent
error_codes: [401, 403, 429, 500, 502, 503, 504]
credential_overrides:
cometapi-pool-1:
error_codes: [400, 429, 500, 502, 503, 504]
error_code_rules:
- code: 400
max_attempts: 3
ban_duration: 15m
The key is the credential's name under credentials:.
Precedence
error_codes: if a credential has acredential_overridesentry with a non-emptyerror_codeslist, that list fully replaces the globalerror_codesfor that credential — it is not merged. A code you want tracked for this credential must be listed here explicitly, even if it's already in the global list.error_code_rules: resolved per status code, most specific first — a rule in the credential's ownerror_code_rulesfor that code wins; otherwise the globalerror_code_rulesentry for that code applies; otherwise the top-levelmax_attempts/ban_durationapply. Rules for codes the credential didn't override keep using the global rule, so you only need to list the codes that actually need different thresholds.- A credential with no
credential_overridesentry behaves exactly as before this feature existed — global settings apply unchanged.
Only credentials with an actual quirk need an entry here; leave the rest out.
Monitoring Bans
Ban and unban events are exported as Prometheus counters (auto_ai_router_credential_ban_events_total, auto_ai_router_credential_unban_events_total, labelled by credential and model — ban events also carry the error code) and logged at ERROR level — losing a credential shrinks routing capacity for its models, so it's worth alerting on.