v3.6.1 · STABLE · PYPI
SDKS · PYTHON

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.

threatdefendr · 3.6.1 Python 3.10+ async + sync Apache-2.0

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", )
Reuse one client. The client holds a connection pool — construct it once at process start and share it. A fresh client per request defeats keep-alive and the retry budget.

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)
ExceptionRaised on
AuthError401 / 403 — token invalid or missing a scope
RateLimitError429 — carries retry_after
NotFoundError404 — unknown id
ApiErrorAny other 4xx / 5xx — carries status, request_id

Where to go next

← ALL SDKS SDK overview NEXT → TypeScript SDK