Kill Switch
The kill switch is an emergency blocking mechanism that runs before any rate-limit rule evaluation. When a request matches a kill switch entry, it is rejected immediately with no further processing.
See Kill Switches configuration for the policy schema. This page covers the algorithm internals.
How it works
Kill switches are the first check in the evaluation pipeline:
request
└─ bundle loaded? (503 if not)
└─ kill switch scan ← evaluated here, BEFORE route matching and rules
└─ route matching
└─ rule evaluation (token bucket, cost budget, …)
└─ allow
This guarantees that a kill switch fires regardless of which policy or rule would have matched.
For each kill switch entry, the engine:
- Extracts the descriptor value from the request using the entry’s
scope_key - Compares the extracted value to
scope_value(string equality, case-sensitive) - If a
routeis present, also checks whether the request path is an exact match - Checks the
expires_attimestamp, if present — expired entries are skipped - If all conditions match: reject immediately
A request must match all conditions in a single entry. Entries are evaluated in declaration order; the first match wins. The scan is O(n) over all entries — expired entries are skipped after a single timestamp comparison.
Configuration
Kill switch entries are defined in the policy bundle. See Kill Switches configuration for the full schema.
Supported scope keys
| Scope key | Source | Example value |
|---|---|---|
jwt:<claim> |
JWT claim extracted from Bearer token | jwt:org_id → "org-abc" |
header:<name> |
HTTP request header | header:x-tenant-id → "tenant-42" |
query:<param> |
URL query parameter | query:api_key → "k_abc123" |
ip:address |
Client IP address | "203.0.113.42" |
ip:country |
GeoIP country code | "TR", "RU" |
ip:asn |
GeoIP ASN number | "AS12345" |
ua:bot |
Bot detection from User-Agent | "true" |
TTL / expiration
The optional expires_at field accepts an ISO 8601 UTC timestamp:
{
"scope_key": "jwt:org_id",
"scope_value": "org-abc",
"expires_at": "2026-03-01T00:00:00Z"
}
The timestamp is parsed into a Unix epoch at bundle load time for zero-cost checking on each request. An entry with an expires_at in the past is silently skipped.
Route scoping
Omitting route makes the entry match any path. Providing route restricts the block to an exact URI path:
{
"scope_key": "ip:country",
"scope_value": "TR",
"route": "/api/v1/completions"
}
This entry only blocks Turkish IPs on /api/v1/completions; other paths are unaffected.
Response headers
When a kill switch fires:
HTTP 429 Too Many Requests
Retry-After: 3600
X-Fairvisor-Reason: kill_switch
The Retry-After value is fixed at 3600 seconds (1 hour). The optional reason field in the kill switch entry is logged but not exposed to the client.
Failure behavior
Kill switches have no per-request mutable state, so there is no shared dict operation on the hot path and no dict-failure code path. If the policy bundle fails to load at startup, Fairvisor returns 503 Service Unavailable for all requests until a valid bundle is loaded.
Shadow mode
In shadow mode, the kill switch check is still performed but the result is recorded as would_reject = true rather than actually rejecting the request. This lets you test a new kill switch entry in production without blocking traffic.
Tuning
The kill switch scan is O(n) where n is the number of entries. For deployments with large entry sets (>100), scope entries with route to reduce the effective scan depth per request path.
Example
{
"kill_switches": [
{
"scope_key": "jwt:org_id",
"scope_value": "org-abc"
}
]
}
This blocks all requests from org org-abc. Add "expires_at": "2026-04-01T00:00:00Z" for a time-limited block.