OPENAPI 3.1 · DOWNLOAD SPEC ↓ · OPEN IN POSTMAN
API REFERENCE

REST API · v1

REST over HTTPS, JSON in and out, predictable URLs, cursor-based pagination, conventional HTTP status codes. Authentication is bearer-token with optional request signing for high-trust integrations.

BASE URL · https://api.threatdefendr.com VERSION · v1 CONTENT · application/json STABILITY · ● stable

Quickstart

Authenticate, then list high-severity events. Pick your language — every example uses the same token and workspace headers.

SHELLcurl
$ curl https://api.threatdefendr.com/v1/events?severity=HIGH \ -H "Authorization: Bearer $TD_TOKEN" \ -H "X-TD-Workspace: ws_acmeProd"
PYTHONlist_events.py
from threatdefendr import Client td = Client(token=os.environ["TD_TOKEN"], workspace="ws_acmeProd") for ev in td.events.list(severity="HIGH"): print(ev.id, ev.source)
TYPESCRIPTlist-events.ts
import { ThreatDefendr } from "@threatdefendr/sdk"; const td = new ThreatDefendr({ token: process.env.TD_TOKEN, workspace: "ws_acmeProd" }); for await (const ev of td.events.list({ severity: "HIGH" })) { console.log(ev.id, ev.source); }
GOlist_events.go
c := td.NewClient(td.Config{Token: os.Getenv("TD_TOKEN"), Workspace: "ws_acmeProd"}) it := c.Events.List(td.EventQuery{Severity: "HIGH"}) for it.Next() { ev := it.Event() fmt.Println(ev.ID, ev.Source) }

Authentication

All requests must include an Authorization: Bearer <token> header. Tokens are scoped — never use a workspace-admin token from a service. Issue scoped tokens at /settings/tokens or via the SDKs.

HTTPcurl
$ curl https://api.threatdefendr.com/v1/events \ -H "Authorization: Bearer $TD_TOKEN" \ -H "X-TD-Workspace: ws_acmeProd"

Request signing (optional, recommended)

For ingestion endpoints and webhooks, requests can be additionally signed with Ed25519. The signature covers the timestamp, method, path, and body. Replays older than 5 minutes are rejected.

HTTPsigned
POST /v1/events:ingest HTTP/1.1 Host: api.threatdefendr.com Authorization: Bearer $TD_TOKEN X-TD-Signature: ed25519=k7Yt…q9 X-TD-Timestamp: 2026-06-20T14:21:08Z Content-Type: application/json { "events": [ ] }

Events

Read raw, enriched events as they flow through the fabric. Cursor-paginated; supports server-sent events for tailing.

GET/v1/eventsList events (paginated)
GET/v1/events/:idFetch a single event by ID
GET/v1/events:streamServer-sent events live tail
POST/v1/events:ingestPush events from a custom source

Sample response · GET /v1/events

JSON200 OK
{ "data": [ { "id": "ev_8XbJq2nP4", "ts": "2026-06-20T14:21:08.214Z", "severity": "HIGH", "source": "endpoint.process", "actor": { "id": "u_8294", "name": "svc-deploy" }, "target": { "id": "h_47193", "host": "db-prod-02" }, "enrichments": { "asset.tier": "crown-jewel", "intel.adversary": "IRONVEIL" } } ], "next_cursor": "cur_2vK4nT0pQ" }

Query parameters

ParameterTypeDescription
severitystringoptionalFilter by LOW · MED · HIGH · CRITICAL.
sourcestringoptionalDotted source selector, e.g. endpoint.process or identity.signin.
actor_idstringoptionalRestrict to a single actor entity.
since · untilRFC 3339optionalTime bounds. Defaults to the last 24 hours.
cursorstringoptionalOpaque cursor from a previous next_cursor.
limitintegeroptionalPage size, 11000. Default 100.
▸ Event object
idstringStable event identifier (ev_…).
tsRFC 3339Event time, millisecond precision, UTC.
severityenumLOW · MED · HIGH · CRITICAL.
sourcestringOriginating stream, dotted.
actor · targetobjectResolved entity refs (id, plus name / host).
enrichmentsobjectFabric-added context — asset tier, intel tags, geo.

Pagination & filtering

List endpoints are cursor-paginated. A response includes next_cursor when more records exist; pass it back as the cursor parameter for the next page. A null cursor means you've reached the end.

SHELLpaginate
$ curl "https://api.threatdefendr.com/v1/events?limit=100&cursor=cur_2vK4nT0pQ" \ -H "Authorization: Bearer $TD_TOKEN"

Filter with the query parameters above; combine freely. Results return newest-first — add sort=asc to reverse. Cursors encode the active filter set, so don't change filters mid-pagination.

Detections

Define, deploy, version, and roll back behavioral detections. Detection-as-code workflows use the YAML format; the API accepts both YAML and compiled JSON plans.

GET/v1/detectionsList all detections in workspace
POST/v1/detectionsDeploy a new detection
GET/v1/detections/:idFetch a detection (with version history)
PATCH/v1/detections/:idUpdate — bumps version, runs dry-run
POST/v1/detections/:id:rollbackRevert to a previous version
DELETE/v1/detections/:idSoft-delete (recoverable for 30 days)

Sample request · POST /v1/detections

JSONrequest
{ "id": "svc-account-from-corp-ip", "title": "Service account authenticated from corp IP", "severity": "HIGH", "plan": { "stream": "identity.signin", "match": { "actor.type": "service_account", "net.src_geo.cidr_label": "corp-egress" }, "window": "5m" }, "on_match": { "create_case": true, "contain": { "action": "disable-actor", "requires_approval": true } } }

Request body

FieldTypeDescription
idstringrequiredStable slug, unique per workspace. Used for updates and rollbacks.
titlestringrequiredHuman-readable name shown on cases and alerts.
severityenumrequiredLOW · MED · HIGH · CRITICAL.
planobjectrequiredThe match logic: stream, match predicate, and window.
on_matchobjectoptionalResponse wiring: create_case, contain, notify.

Cases

Investigation timelines, including roll-ups across detections, response actions, and analyst notes. Cases bind to entities, not to single events.

GET/v1/casesList cases (filter by status, owner, severity)
GET/v1/cases/:idFetch a case (full timeline)
PATCH/v1/cases/:idUpdate status, owner, severity, notes
POST/v1/cases/:id:assignReassign + notify
POST/v1/cases/:id:rollbackReverse all response actions in the case

Sample response · GET /v1/cases/:id

JSON200 OK
{ "id": "case_7Qd2Rk9", "status": "open", "severity": "HIGH", "title": "Service account from corp egress", "owner": "alex@acme.io", "entities": [ "u_8294", "h_47193" ], "timeline": [ { "ts": "2026-06-20T14:21:08Z", "kind": "detection", "ref": "det_2vK4nT" }, { "ts": "2026-06-20T14:21:41Z", "kind": "action", "ref": "act_9bc01" } ], "created_at": "2026-06-20T14:21:08Z" }

Query parameters

ParameterTypeDescription
statusenumoptionalopen · investigating · contained · closed.
ownerstringoptionalFilter by assigned analyst.
severityenumoptionalLOW · MED · HIGH · CRITICAL.
since · untilRFC 3339optionalFilter by case creation time.
cursor · limitstring · intoptionalStandard cursor pagination.
▸ Case object
idstringStable case identifier (case_…).
statusenumLifecycle state — open through closed.
entitiesarrayEntity ids the case binds to — actors and targets.
timelinearrayOrdered events, detections, and actions with refs.
ownerstringAssigned analyst; null when unassigned.

Containment

Direct, idempotent response actions. Every call records its inverse in the case timeline so undo is single-call.

POST/v1/contain:isolate-hostNetwork-isolate via EDR
POST/v1/contain:disable-actorDisable identity at IdP
POST/v1/contain:revoke-tokenRevoke OAuth grants & sessions
POST/v1/contain:block-domainBlock at the proxy / DNS
POST/v1/contain:rollbackInvert a previous contain action

Sample request · POST /v1/contain:isolate-host

JSONrequest
{ "host_id": "h_47193", "reason": "IRONVEIL signed-driver match", "case_id": "case_7Qd2Rk9", "requires_approval": false }

Sample response · 200 OK

JSONaction recorded
{ "action_id": "act_9bc01", "type": "isolate-host", "state": "applied", "inverse": "release-host", "case_id": "case_7Qd2Rk9", "applied_at": "2026-06-20T14:21:41Z" }

Request body

FieldTypeDescription
host_id · actor_idstringrequiredTarget of the action; which field depends on the endpoint.
reasonstringrequiredFree-text justification, written to the case timeline.
case_idstringoptionalAttach the action to an existing case.
requires_approvalbooleanoptionalQueue for analyst sign-off instead of acting immediately.

Every action returns an inverse — the single endpoint that undoes it. Replay that endpoint, or call /v1/contain:rollback with the action_id, to reverse cleanly.

Idempotency

Every state-changing POST accepts an Idempotency-Key header. Reusing a key within 24 hours returns the original response instead of acting again — safe to retry on network failures without double-containing a host.

HTTPidempotent
$ curl "https://api.threatdefendr.com/v1/contain:isolate-host" \ -H "Authorization: Bearer $TD_TOKEN" \ -H "Idempotency-Key: 5f3c…b1" \ -d '{"host_id":"h_47193"}'

Keys are scoped per endpoint and token. A retried key with a different body returns 409 Conflict.

Adversaries

Read-only access to the tracked-adversary catalog. Updated continuously by the Intelligence Desk.

GET/v1/adversariesList 240 tracked adversaries
GET/v1/adversaries/:codeFull dossier · TTPs · IOCs · timeline
GET/v1/adversaries/:code/iocsLive IOC feed (TAXII / STIX 2.1)

Sample response · GET /v1/adversaries/:code

JSON200 OK
{ "code": "IRONVEIL", "aliases": [ "APT-4471", "Silent Forge" ], "motivation": "espionage", "first_seen": "2023-11-02", "last_seen": "2026-06-18", "ttps": [ "T1059.001", "T1078", "T1021.006" ], "ioc_count": 1284, "confidence": "high" }
▸ Adversary object
codestringStable tracking codename.
aliasesarrayNames used by other vendors and reports.
ttpsarrayMITRE ATT&CK technique ids.
ioc_countintegerLive indicator count; pull the feed at /iocs.
confidenceenumAttribution confidence — low · medium · high.

Webhooks

Receive case-state and detection-match events at your HTTPS endpoint. Webhooks are Ed25519-signed; verify with the SDK or the snippet below.

GET/v1/webhooksList configured webhooks
POST/v1/webhooksCreate endpoint subscription
POST/v1/webhooks/:id:rotate-secretRotate signing key with 30d overlap

Verifying a webhook signature

PYTHONverify.py
from threatdefendr import verify_webhook @app.post("/td/webhook") def incoming(req): body = req.get_data() sig = req.headers["X-TD-Signature"] ts = req.headers["X-TD-Timestamp"] verify_webhook(secret=WH_SECRET, body=body, signature=sig, timestamp=ts) handle(json.loads(body))

Event types

EventFires when
detection.matchedA detection fires against the live stream.
case.openedA new case is created.
case.updatedStatus, owner, or severity changes.
action.applied · action.reversedA containment action is taken or undone.

Sample payload · case.opened

JSONdelivered to your endpoint
{ "event": "case.opened", "delivery_id": "dlv_8Xa2Qr", "ts": "2026-06-20T14:21:08Z", "data": { "case_id": "case_7Qd2Rk9", "severity": "HIGH", "title": "Service account from corp egress" } }

Deduplicate on delivery_id and verify the X-TD-Signature header before processing — see the webhook security guide.

Errors

The API uses conventional HTTP status codes. All 4xx and 5xx responses share a single envelope:

JSONerror envelope
{ "error": { "code": "invalid_detection_plan", "message": "`window` must be a duration ≤ 24h", "field": "plan.window", "request_id": "req_2vK4nT0pQ" } }
StatusMeaningTypical fix
400Validation errorRead error.field and resubmit.
401Missing or invalid tokenRefresh credentials.
403Token lacks required scopeRe-issue with broader scope.
404Resource doesn't exist or not visibleCheck workspace scope.
409Conflict (idempotency / state)Retry with new Idempotency-Key.
429Rate-limitedHonor Retry-After.
5xxServer-sideRetry with exponential back-off.

Rate limits

Per-token bucket; refilled continuously. Limits are returned on every response:

HTTPheaders
X-RateLimit-Limit: 300 X-RateLimit-Remaining: 287 X-RateLimit-Reset: 1719256968 X-RateLimit-Bucket: events.read
BucketDefaultBurst
events.read300 / min600
events.ingest20,000 / s40,000
detections.write60 / min120
cases.write120 / min240
contain.write30 / min60

Enterprise customers get custom limits — talk to your account team or open a ticket with the workload profile.

Versioning & stability

The API is versioned in the URL path (/v1). Backward-compatible changes — new fields, endpoints, and enum values — ship without a version bump, so write tolerant parsers. Breaking changes ship under a new major version.

TierMeaningChange policy
● stableProduction-ready, SLA-backedNo breaking changes within a major version.
◐ betaUsable; shape may still shiftTwo weeks' notice before breaking changes.
○ experimentalPreview, opt-in via headerMay change or be withdrawn without notice.

Deprecations are announced at least six months ahead. Sunset endpoints return a Sunset header with the removal date and a link to the migration guide.

← PREV Architecture NEXT → SDKs