API reference
REST. JSON in, JSON out. Bearer auth.
Base URL
All endpoints below are rooted at {PUBLIC_URL}/api/v1.
Authentication
Generate an API key under Settings > API keys in the UI. Send it as a Bearer token:
curl -H "Authorization: Bearer $IR_API_KEY" https://ir.example.com/api/v1/alertsErrors
Standard HTTP codes. Body is { "error": "code", "message": "human-readable", "details": {...} }.
| Code | Meaning |
|---|---|
400 | Validation error - see details. |
401 | Missing or invalid bearer token. |
403 | Token doesn't have the required scope/role. |
404 | Entity doesn't exist (or you can't see it). |
409 | Idempotency conflict. |
423 | License grace period exceeded - org locked. |
429 | Rate limit; retry after Retry-After seconds. |
Alerts
POST /alerts - ingest an alert
{
"source": "crowdstrike",
"external_id": "falcon-9182734",
"severity": "high",
"title": "Suspicious PowerShell on WS-042",
"detected_at": "2026-06-02T09:14:00Z",
"asset": { "hostname": "WS-042", "owner": "[email protected]" },
"observables": [
{ "type": "ip", "value": "203.0.113.4" },
{ "type": "hash", "value": "da39a3ee..." }
],
"raw": { /* original payload */ }
}Idempotent on (source, external_id). Returns { "id": "alert_...", "deduplicated": false }.
GET /alerts
Filter: ?status=triage&severity=high&source=crowdstrike&cursor=.... Returns paginated list.
POST /alerts/{id}/dismiss
Body: { "reason_code": "known-good", "comment": "..." }.
POST /alerts/{id}/escalate
Body: { "case_id": null | "case_..." }. Null creates a new case carrying the alert's observables and severity.
Cases
POST /cases
Create a case directly (without escalating an alert). Same shape minus the alert-specific fields.
GET /cases/{id}
Returns the full case payload including timeline, observables, evidence list, ATT&CK mapping.
POST /cases/{id}/comments
Body: { "body": "markdown supported", "mentions": ["user_..."] }.
POST /cases/{id}/attachments
Multipart upload. Max 50 MB by default (raise client_max_body_size in your proxy if needed).
POST /cases/{id}/resolve
Body: { "verdict": "true_positive" | "false_positive" | "benign" | "duplicate", "root_cause": "...", "narrative": "..." }. Generates the report.
Observables
GET /observables/search?type=ip&value=203.0.113.4
Find every alert and case touching this observable. Useful for "have we seen this IP before?" lookups from external tools.
POST /observables/suppress
Body: { "type": "domain", "value": "stats.example.com", "scope": "global" | "source:edr", "until": "2026-12-31T00:00:00Z" }.
Webhooks
Outbound webhooks fire on case transitions. Configure under Settings > Webhooks. Payload:
{
"event": "case.resolved",
"case_id": "case_01HZ...",
"verdict": "true_positive",
"at": "2026-06-02T11:42:00Z",
"by": "user_01HY..."
}Signed with X-IR-Signature: sha256=<hex> using your webhook secret. Retried with exponential backoff up to 24 h.
Healthcheck
GET /healthz returns {"status":"ok","db":"ok","ch":"ok","license":"company","grace_h":0}. Unauthenticated. Use it for your monitoring probes.