The Python SDK.
Async-first and fully typed, generated from the OpenAPI spec and hand-finished for ergonomics. The same client streams events, drives detections and cases, and runs containment — with retry, idempotency, and pagination handled for you.
Install
The package is published to PyPI as threatdefendr. It has no required system dependencies and ships type stubs.
SHELLinstall $ pip install threatdefendr # or: uv add threatdefendr . poetry add threatdefendr
Authenticate
A client needs a token and a workspace. Read the token from the environment — never commit it. For non-interactive use, mint a scoped service token under /settings/tokens and rotate it quarterly.
PYTHONclient.py import os from threatdefendr import Client td = Client( token=os.environ["TD_TOKEN"], workspace="acme-prod", )
Quickstart
Stream high-severity events as they land, then open a case from a detection. Reads are lazy; writes return the created resource.
PYTHONtail + open a case # live tail, newest first for ev in td.events.tail(severity="HIGH"): print(ev.ts, ev.id, ev.actor.name, ev.ctx.get("intel.adversary")) # open a case from a detection case = td.cases.create( severity="HIGH", title="Service account from corp egress", detections=["det_2vK4nT"], ) print(case.id)
Pagination & async
List endpoints return a lazy iterator that pages transparently — loop and forget. When you need the cursor, drop to the page object. Every call has an Async twin under AsyncClient.
PYTHONauto-pagination + async # iterate every live detection across all pages for det in td.detections.list(state="LIVE"): print(det.id, det.title) # or hold the cursor yourself page = td.events.search(query="actor.type:service_account", limit=100) print(len(page.items), page.next_cursor) # async: stream concurrently import asyncio from threatdefendr import AsyncClient async def main(): async with AsyncClient(workspace="acme-prod") as td: async for ev in td.events.tail(severity="HIGH"): await handle(ev) asyncio.run(main())
Retries & idempotency
The client retries 429 and 5xx with exponential backoff and honors Retry-After. Writes accept an idempotency key, so a retried create never produces a duplicate.
PYTHONresilient writes td = Client( token=os.environ["TD_TOKEN"], workspace="acme-prod", max_retries=5, # exponential backoff on 429 / 5xx timeout=30.0, ) td.cases.create( title="oncall handoff", severity="MEDIUM", idempotency_key="oncall-2026-06-24", )
Errors
Every failure raises a typed exception off a common base, so you can catch precisely or broadly. The request id on ApiError is what support will ask for.
PYTHONhandling failures from threatdefendr import ApiError, AuthError, RateLimitError, NotFoundError try: td.contain.isolate_host(host_id="h_47193", reason="IRONVEIL match") except RateLimitError as e: sleep(e.retry_after) # seconds until the window resets except AuthError: refresh_token() except NotFoundError: pass except ApiError as e: log.error("td api %s req=%s", e.status, e.request_id)
| Exception | Raised on |
|---|---|
AuthError | 401 / 403 — token invalid or missing a scope |
RateLimitError | 429 — carries retry_after |
NotFoundError | 404 — unknown id |
ApiError | Any other 4xx / 5xx — carries status, request_id |
Where to go next
- API reference — every endpoint these methods wrap.
- Webhook security — verify inbound deliveries.
- SDK overview — feature parity across runtimes.