---
title: "List detection runs"
method: GET
path: "/documents/{documentId}/detect"
tags: ["Detection"]
---

# List detection runs

`GET /documents/{documentId}/detect`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/documents/{documentId}/detect`
- Auth: HTTP bearer

The document's detection history, newest first. Forcing a re-run never replaces the run before it, so this is where "what did detection say last time, and what changed" is answered, both for audit and for comparing two agent versions on the same document.

Each entry is a summary: whether the run was forced, which agent version produced it, what it cost and where that number came from, which model answered, and the run's timestamps. The detected fields, the formatting assessment, and the per-field reasoning live on `GET /documents/{documentId}/detect/{runId}`.

## Query parameters

- `page` integer
- `limit` integer

## Response `200`

A page of detection runs, newest first.

- object
  - `data` DetectionRunSummary[], required
    - `detectionRunId` integer, required
    - `status` 'processing' | 'completed' | 'failed', required: A detection run's lifecycle. `processing` while it is queued or running, then `completed` or `failed`.
    - `forced` boolean, required: Whether this run was explicitly asked for instead of a cached one. Runs recorded before forcing existed read as false.
    - `agentVersion` string, nullable, required: Which detection pipeline produced the run. Comparing two versions on one document is what this history is for.
    - `costUsd` number, nullable, required: What the run actually spent, in USD. Null until it finishes.
    - `costSource` 'actual' | 'estimated' | 'mixed' | 'null', nullable, required: Where `costUsd` came from: `actual` when the provider reported it, `estimated` when a conservative estimate was required, `mixed` when the run used both. Null until the run finishes.
    - `reservedCostUsd` number, required: What was held against the workspace's daily cap for this run, in USD.
    - `primaryModelUsed` string, nullable, required: The model that answered the primary classify and detect pass.
    - `modelRequested` string, required: The model the deployment asked for. It can differ from what answered when the cascade fell through.
    - `createdAt` string, date-time, required
    - `startedAt` string, date-time, nullable, required
    - `completedAt` string, date-time, nullable, required
  - `pagination` Pagination, required: Where the returned page sits in the full result set.
    - `page` integer, required: The 1-based page returned.
    - `limit` integer, required: Items per page, as requested.
    - `total` integer, required: Total items matching the query.
    - `totalPages` integer, required: `ceil(total / limit)`.

## Other responses

- `400`: The request is malformed or the resource is in the wrong state for a synchronous check. Codes: `validation_error`, `invalid_body`, `invalid_parameter`, `invalid_idempotency_key`, `invalid_event_id`, `invalid_file`, `invalid_url`, `invalid_status`, `key_mismatch`, `no_signers`, `not_completed`, `envelope_exists`, `document_not_ready`, `upload_failed`.
- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
