knoxcall is the official KnoxCall client for Python. One import gives you a sync client (thread- and fork-safe, share it as a module-level singleton) or an async client for FastAPI and friends. Every method’s return shape is a TypedDict in knoxcall.types, so IDEs and mypy see the exact server fields.
Install
knoxcall on PyPI. Source: github.com/KnoxCall/sdk-python.
Create a client
KNOXCALL_ACCESS_TOKEN / KNOXCALL_API_KEY), the credentials file written by knoxcall login (~/.knoxcall/credentials.json), workload identity (GitHub Actions, GCP, AWS IRSA, Azure Managed Identity, Vercel, CircleCI — no stored secrets in CI), then KNOXCALL_CLIENT_ID + KNOXCALL_CLIENT_SECRET.
Legacy API keys — a pre-acquired key or token still works everywhere: KnoxCall(api_key="tk_live_...") accepts a minted kc_… token or a legacy tk_… / AKE… key. It is sent as-is and never auto-renewed — prefer knoxcall login or client credentials for new integrations.
Log in once with the CLI
The package ships theknoxcall command. Sign in once in your browser and every script, notebook, and agent on the machine picks the credential up automatically — no env vars, no keys in code:
~/.knoxcall/credentials.json (file mode 0600). The stored tenant and base URL seed the client automatically; explicit constructor arguments or env vars always win. Access tokens refresh themselves, and refreshes are cross-process safe (file lock + atomic rotation of the single-use refresh token). If a refresh fails because the credential was revoked or expired, the SDK raises AuthenticationError telling you to run knoxcall login again. Select a non-default profile with KNOXCALL_PROFILE (or StoredCredentials(profile=...)). The same file is picked up by all five SDKs — log in once, use any language.
Manage resources
Create a route, then list with pagination — single-object methods returndata unwrapped; paginated lists return the full {"data": [...], "meta": {...}} page; iterate() walks every page:
await. The same pattern covers every resource: secrets, webhooks, clients, oauth_clients, environments, api_keys, account, audit_logs, agents, crypto, pki, vaults, and dynamic_db.
Call routes through the proxy
client.call() proxies a request through a KnoxCall route to your upstream and returns the raw httpx.Response — the upstream’s HTTP status belongs to you; the SDK never turns it into an error. Reference routes by slug (write-once, rename-proof); UUIDs also work.
path is the upstream path. On a KnoxCall cloud tenant host the data plane is served under /api (https://{tenant}.knoxcall.com/api/<path>); the SDK adds that prefix itself whenever the proxy base is a cloud tenant host with no path of its own, and uses any other base verbatim (self-hosted, or an override that already carries a path). So /api/v2/tickets reaches an upstream path that itself begins with /api.
Bound routes
State the route (and optional defaults) once withclient.route(), then use plain HTTP verbs:
await printnode.get(...)). The handle holds no state beyond the defaults — retries, token refresh, and 401 re-mint behave exactly as on call().
Route-aware interception (preview)
Send an untouched third-party SDK’s traffic through the Route that covers it — and through the ephemeral proxy where no Route does — with no per-SDK wiring. Turn the Route’s Intercept toggle on and it takes effect on the next request after the 60 s TTL, or on the next refusal, with no code change. The full decision order, what is and is not reached, and the hooks are on Route-aware interception.httpx (sync and async clients on the default transport) and urllib3 (requests, OpenAPI-generated clients, botocore). aiohttp is opt-in on the async client — KnoxCallAsync(...).wrap.intercept(stacks=["aiohttp"]) patches aiohttp.ClientSession._request (aiohttp 3.13 or newer, installed separately) and answers with a real aiohttp.ClientResponse; a WebSocket handshake goes direct. Not reached: an explicitly injected custom transport, raw http.client, pycurl — use knox.wrap.transport(routes="auto") for a transport you inject, which makes the same decisions (ready(), refresh(), manifest()). require_context=True limits interception to code inside with knox.wrap.routed():. Per-host options: hosts={"api.resend.com": {"credential": {"secret": "resend-key"}}} (escrow) or {"unavailable": "direct"} (transit only). KNOXCALL_INTERCEPT=off is the kill switch.
Verify webhooks
construct_webhook_event verifies a delivery AND returns the parsed, typed event in one step. Pass the RAW request body (never re-serialized JSON):
format= matches the webhook’s configured hmac_format: "legacy" (default), "stripe", "github", "slack", "aws-sns", or "custom" (pass header_name=). tolerance_seconds (default 300) bounds replay; pass None to disable. Also available as client.webhooks.construct_event() on both facades.
Handle errors
PermissionDeniedError. Transport failures map to APIConnectionError / APIConnectionTimeoutError. Every API error carries the server’s request_id — quote it when contacting support.
Retries and idempotency
Management requests retry automatically on 408/429/500/502/503/504 with exponential backoff + jitter (3 attempts by default; never 409;Retry-After honored on 429, capped at 30s). Mutating requests get an auto-generated ULID idempotency key so retries are safe. A 401 triggers one transparent token re-mint. Data-plane transport failures retry only when safe: connection-refused always, later failures (read timeout, keepalive reset) only for GET/HEAD — a mutating request is never replayed.
DPoP
cnf.jkt, and signs a fresh proof on every request — both management calls and proxy call() requests. In the default "auto" mode it upgrades automatically when the OAuth client requires DPoP.
Full reference
The package README documents every resource method, the ephemeral proxy, Redis token store, request-body encoding (datetime / Decimal / UUID / set out of the box), thread-safety details, and credential-less signup() / signup_sync(): github.com/KnoxCall/sdk-python.