Investigations API
Investigations are the core resource in Puck. You describe what you want to know in plain English; the brain routes the query through calibrated detections, a pathfinder agent, or both, and returns a narrative with findings and a severity rating. Every investigation run is asynchronous — the trigger endpoint returns immediately and results stream via webhooks or polling.
Related concept pages: Investigations, Pathfinder vs Fleet.
POST /v1/investigations
Submit a natural-language investigation. Returns 202 Accepted immediately with a queued investigation ID and an estimated completion time. The actual run is asynchronous; follow progress by polling GET /v1/investigations/{id} or by listening for investigation.queued and investigation.completed webhook events.
Scope required: investigations:trigger
Idempotency: Pass an Idempotency-Key header or include idempotency_key in the body to make the request safely retryable within 24 hours. The same key with the same body returns the original 202 response. The same key with a different body returns 409 Conflict.
Freshness: When mode is auto (default) or cached, the response includes a freshness block describing how stale the graph data is. If freshness.recommendation.should_rerun_live is true, consider issuing a follow-up trigger with mode: "live" using the recommended_scope from the recommendation.
# curlcurl https://api.puck.security/v1/investigations \ -H "Authorization: Bearer $PUCK_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "find unauthorised AI tools on engineering laptops", "depth": "standard", "scope": { "type": "selector", "filter": { "tags": { "team": "engineering" } } } }'// Node/TSconst res = await fetch("https://api.puck.security/v1/investigations", { method: "POST", headers: { Authorization: `Bearer ${process.env.PUCK_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ query: "find unauthorised AI tools on engineering laptops", depth: "standard", scope: { type: "selector", filter: { tags: { team: "engineering" } }, }, }),});if (!res.ok) throw new Error(`trigger failed: ${res.status}`);const { investigation_id, estimated_completion_seconds, links } = await res.json();No request parameters or body.
Status: 202 — Accepted; investigation queued.
Empty response body.
| Status | Meaning |
|---|---|
400 | Validation error. |
401 | Missing or invalid API key. |
403 | API key lacks `investigations:trigger`. |
409 | Idempotency key reused with a different request body. |
GET /v1/investigations
List all investigations for the account, sorted by started_at descending. The response is cursor-paginated; pass the opaque next_cursor value as ?cursor= to fetch the next page.
Scope required: investigations:read
Filtering: Pass ?status=running (or any InvestigationStatus value) to restrict results. Useful for polling the active set.
# First page of completed investigationscurl "https://api.puck.security/v1/investigations?status=completed&limit=25" \ -H "Authorization: Bearer $PUCK_API_KEY"let cursor: string | null = null;const investigations = [];
do { const url = new URL("https://api.puck.security/v1/investigations"); url.searchParams.set("status", "completed"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.PUCK_API_KEY}` }, }); const data = await res.json(); investigations.push(...data.investigations); cursor = data.next_cursor;} while (cursor !== null);| Field | In | Type | Required | Description |
|---|---|---|---|---|
status | query | object | no | |
cursor | query | string | no | |
limit | query | integer | no |
Status: 200 — List of investigations.
Empty response body.
No documented error responses for this endpoint.
GET /v1/investigations/{id}
Fetch the current state of a single investigation. Poll this endpoint to track progress or retrieve the final narrative once the investigation reaches completed or failed.
Scope required: investigations:read
The progress block is populated from the moment the investigation starts dispatching plans. The summary block (narrative + severity) is null until the investigation reaches completed.
curl "https://api.puck.security/v1/investigations/inv_01hx…" \ -H "Authorization: Bearer $PUCK_API_KEY"const res = await fetch(`https://api.puck.security/v1/investigations/${id}`, { headers: { Authorization: `Bearer ${process.env.PUCK_API_KEY}` },});const inv = await res.json();console.log("status:", inv.status, "severity:", inv.summary?.severity);No request parameters or body.
Status: 200 — Investigation detail.
Empty response body.
| Status | Meaning |
|---|---|
404 | Investigation not found. |
DELETE /v1/investigations/{id}
Cancel a running investigation. This is idempotent — cancelling an already-terminal investigation returns 200 with the existing status rather than an error.
Scope required: investigations:trigger
Once cancelled, the investigation cannot be resumed. Partial results up to the cancellation point are preserved.
curl -X DELETE "https://api.puck.security/v1/investigations/inv_01hx…" \ -H "Authorization: Bearer $PUCK_API_KEY"const res = await fetch(`https://api.puck.security/v1/investigations/${id}`, { method: "DELETE", headers: { Authorization: `Bearer ${process.env.PUCK_API_KEY}` },});const { ok, status } = await res.json();console.log("status after cancel:", status);No request parameters or body.
Status: 200 — Cancelled (or already terminal).
| Field | Type | Required | Description |
|---|---|---|---|
ok | boolean | no | |
status | object | no |
| Status | Meaning |
|---|---|
404 | Investigation not found. |