TokenPolice
Docs
SDK reference

init() parameters

Every tp.init() option in both SDKs, what firewall mode does, and the three environment variables.

Call tp.init() once at startup, before your provider clients are imported. In practice you set three things — the key, the collector URL, and the firewall mode. Everything else has a sensible default.

import token_police as tp
import os

tp.init(
    api_key=os.environ["TOKENPOLICE_API_KEY"],
    base_url="https://collect.tokenpolice.ai",
    firewall="dry_run",   # watch first; flip to "enforce" when ready
)
import * as tp from "token-police";
import OpenAI from "openai";

tp.init({
  apiKey: process.env.TOKENPOLICE_API_KEY,
  baseUrl: "https://collect.tokenpolice.ai",
  firewall: "dry_run",
  instrumentModules: { openAI: OpenAI },   // Node: see Node & ESM
});

https://collect.tokenpolice.ai is also the built-in default in both SDKs, so you can leave base_url out. Set it anyway — an explicit URL is what makes the destination visible in your repo, and it's the one line you change to point at a self-hosted or local collector.

The firewall mode

This is the dial that decides whether TokenPolice can ever stop a call. It takes exactly three values, spelled in lower case:

firewallPre-call checkOn a block decisionWhat your app sees
"dry_run" (default)Runs in fullRecorded as a would-blockNothing. The call proceeds.
"enforce"Runs in fullRaised into your codeTokenPoliceBlockedError — catch it
"off"SkippedNothing. No rule can act.

A few things worth knowing:

  • "off" is not "disabled". Usage is still logged for every provider, so your dashboard and cost history keep filling up. Only the check and enforcement stop.
  • "dry_run" runs the whole check, including the network round-trip — that's what makes its would-block records trustworthy. Budget for up to timeout of extra latency per call (0.5 s by default), and it fails open if the collector is slow.
  • The value is matched byte for byte. "Enforce" or "DRY_RUN" is not recognised: the SDK falls back to "dry_run" — never to "enforce" — so a typo can never start blocking traffic by accident. Node prints a warning; Python only prints it when log_errors=True.
  • A reroute is enforcement too. A rule that swaps the model only takes effect under "enforce"; in dry-run it is recorded and nothing changes.

Two dials, not one. Setting firewall="enforce" is only half of it. Each Block or Reroute rule you create also has its own Enforce / Dry-run setting in the dashboard, and a call is blocked only when both say enforce. See Dry-run vs enforce.

The one exception to fail-open. If TokenPolice becomes unreachable, calls go through — with one deliberate exception: under "enforce" on a daemon deployment, something the firewall had already blocked stays blocked through the outage. An outage can't be used to lift a limit you set. The exception needs the SDK's open update stream, which it opens only when deployment is "daemon"; on "serverless" and "edge" there is no stream and an outage fails fully open. See Fail-open.

Python parameters

ParameterTypeDefaultWhat it does
api_keystrenv TOKENPOLICE_API_KEY, else raisesYour tp_sk_… key. A key that doesn't start with tp_sk_ only warns.
base_urlstrenv TOKENPOLICE_BASE_URL, else https://collect.tokenpolice.aiCollector URL. Trailing slashes are stripped.
firewallstr"dry_run""dry_run" / "enforce" / "off" — see above.
timeoutfloat2.0Max seconds the SDK waits for TokenPolice before letting the call through anyway. Python takes the value as given — it isn't validated or clamped (Node's is).
log_errorsboolFalseTurn on the SDK's own diagnostic output. An integration aid — turn it back off when you're done.
capture_stream_usageboolonMakes streamed calls report token counts. Off, a streamed call's cost is lost. TP_CAPTURE_STREAM_USAGE=0 disables it.
error_detailstr"redacted"How much of a failed provider call's error text leaves your process: "none" (classifier only), "redacted" (exception class + a hash of the message), "raw" (the verbatim, truncated string). Unknown values become "redacted".
deploymentstr"auto""auto" / "daemon" / "serverless" / "edge". Auto-detection is usually right, and setting it does not flush for you — see Flushing & serverless.
max_workersintCPU-basedPython only. Size of the background pool that ships usage logs off your request path.
tracer_providerOTel providerNonePython only. Attach TokenPolice to an OpenTelemetry provider you already run instead of the private one it builds.
sse_reconnect_max_interval_secondsint300Cap on the backoff between attempts to re-establish the rule feed. Rarely set.
stream_stale_grace_secondsfloat60Clamped to 0–3600. How long the SDK keeps trusting its cached rule view after the feed drops, before re-checking a matching call with the server. 0 re-checks from the first missed moment.
enforceboolDeprecated alias — don't use it. Use firewall.

Node parameters

Same options, camelCased.

OptionTypeDefaultWhat it does
apiKeystringenv TOKENPOLICE_API_KEY, else throwsYour tp_sk_… key. A key that doesn't start with tp_sk_ only warns.
baseUrlstringenv TOKENPOLICE_BASE_URL, else https://collect.tokenpolice.aiCollector URL. Trailing slashes are stripped.
firewallFirewallMode"dry_run"Same three values, same byte-exact matching, same safe fallback.
timeoutnumber2.0Max seconds. A non-positive or non-finite value warns and falls back to 2.0.
logErrorsbooleanfalseSame as Python; also un-gates the SDK's debug lines.
captureStreamUsagebooleanonSame as Python; TP_CAPTURE_STREAM_USAGE=0 disables.
errorDetailErrorDetailMode"redacted"Same three modes and the same safe coercion as Python.
deploymentstring"auto"Same as Python, including edge runtimes.
instrumentModulesInstrumentModulesNode only. Hand the SDK the provider modules your app already imported — see Node & ESM.
sseReconnectMaxIntervalSecondsnumber300Same as Python.
streamStaleGraceSecondsnumber60Same as Python, same 0–3600 clamp.
enforcebooleanDeprecated alias — don't use it. Use firewall.

Node has no maxWorkers or tracerProvider (it's fetch-based, with no thread pool).

Call init() once per process. Calling it again replaces the client and says so in the log — and after uninstrument() a second init() can't re-attach at all. Other than a missing API key, init() never throws: if anything inside it fails, the SDK degrades instead of taking your app down.

Environment variables

Three, and three only:

VariableReplaces
TOKENPOLICE_API_KEYapi_key / apiKey
TOKENPOLICE_BASE_URLbase_url / baseUrl
TP_CAPTURE_STREAM_USAGEcapture_stream_usage / captureStreamUsage (0 disables)

The prefix is TOKENPOLICE_, one word. TOKEN_POLICE_API_KEY (with an underscore) is read by nothing — a common copy-paste trap.

Next