Errors: TokenPoliceBlockedError
The one exception TokenPolice ever raises into your code, its fields, and exactly when it fires.
TokenPolice is fail-open by design: if anything inside the SDK or the collector goes wrong, your
LLM call still runs. There is exactly one exception it will ever raise into your code —
TokenPoliceBlockedError — and only on a deliberate block.
When it fires
Only when both dials say enforce: the SDK is firewall="enforce" and the rule that matched
is set to Enforce, and that rule's decision is to block. In every other case — dry-run on
either dial, a check timeout, a collector outage — nothing is raised and the call proceeds.
See Dry-run vs enforce for the full truth table.
Catching it
Wrap the call and read the structured fields.
import token_police as tp
try:
resp = client.chat.completions.create(...)
except tp.TokenPoliceBlockedError as e:
print(e.reason) # human-readable reason
print(e.rule_id) # which rule blocked
print(e.kind) # what kind of block (e.g. "budget", a loop detector)
print(e.trace_id) # the trace this decision belongs toimport * as tp from "token-police";
try {
const resp = await client.chat.completions.create(/* ... */);
} catch (e) {
if (e instanceof tp.TokenPoliceBlockedError) {
console.log(e.reason, e.ruleId, e.kind, e.traceId);
}
}The fields
| Python | Node | What it is |
|---|---|---|
reason | reason | Human-readable reason for the block. |
rule_id | ruleId | The rule that produced the decision. |
kind | kind | The block category — e.g. "budget", or a loop-detector name. |
trace_id | traceId | The trace this run belongs to. |
This is the only error to catch. Everything else about TokenPolice is fail-open — a network
blip, a bad response, the collector being down: your call goes through untouched. Handling
TokenPoliceBlockedError is all the error handling you need.

