Skip to content

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.

Terminal window
# curl
curl 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/TS
const 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.

StatusMeaning
400Validation error.
401Missing or invalid API key.
403API key lacks `investigations:trigger`.
409Idempotency 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.

Terminal window
# First page of completed investigations
curl "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);
FieldInTypeRequiredDescription
statusqueryobjectno
cursorquerystringno
limitqueryintegerno

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.

Terminal window
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.

StatusMeaning
404Investigation 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.

Terminal window
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).

FieldTypeRequiredDescription
okbooleanno
statusobjectno
StatusMeaning
404Investigation not found.