---
title: "List Code Scan Runs"
method: GET
path: "/v1/projects/{project_id}/code-scan/runs"
tags: ["code-scan"]
---

# List Code Scan Runs

`GET /v1/projects/{project_id}/code-scan/runs`

## Request

- Base URL: `https://api.sonenta.dev`
- URL: `https://api.sonenta.dev/v1/projects/{project_id}/code-scan/runs`
- Auth: none declared

Scan-run HISTORY, newest first — one item per ingest (Viewer+).

The read side of the per-scan recap `summary` only shows for the LAST run.
Every ingest has always appended its bilan (`counts{new, persisting,
treated, gone}`, `submitted`, `via`, `meta`, actor) to this history; this
endpoint paginates it, `created_at DESC`, over the `project_recent` index.

Cursor pagination: read `cursor_next` and pass it back as `cursor` until it
is null. The cursor is a COMPOUND (created_at, uuid) token — it names every
field the sort orders on, so no run is skipped even when two share a
millisecond. An EMPTY history means no scan has ever run here — an unmeasured
project, not a clean one.

## Path parameters

- `project_id` string, required

## Query parameters

- `cursor` string, nullable: Opaque page token: pass back the `cursor_next` from the previous page. Omit for the first page. It carries the last row's (created_at, uuid) — do not parse it; a malformed value is a 400.
- `limit` integer

## Response `200`

Successful Response

- CodeScanRunsPage
  - `items` CodeScanRunHistoryItem[], required
    - `scan_id` string, required
    - `created_at` string, date-time, required
    - `submitted` integer, required: How many findings this run posted.
    - `counts` CodeScanRunCounts, required: Per-RUN figures. `persisting` lives here and NOWHERE else: it is not a stored status (those are new/treated/gone), it is what one run observed — "known, still present, not treated". A finding that persists stays `new`, which is why the summary's top-level counts have no `persisting` column.
      - `new` integer, required
      - `persisting` integer, required
      - `treated` integer, required
      - `gone` integer, required
    - `via` string, required: Which surface ingested it: `core` or `mcp`.
    - `confirm_mass_gone` boolean: True when this run retired most of the project's live findings and a human explicitly confirmed the wipe (the innocent cause being a partial checkout).
    - `meta` object: Verbatim scanner context (e.g. tool / version / sha) — a passthrough the backend guarantees NO key in. Treat every field, and the object itself, as optional; the dashboard renders it as a discreet secondary line.
    - `actor` CodeScanRunActor: Display-ready attribution — the dashboard renders the `label`, never the raw `uuid` (which stays for an audit join). `label` is a human name resolved at BOUNDED per-page cost (a user's name/email, or a key's `name · prefix…last4`). A uuid that no longer resolves keeps its `type`/`uuid` and gets an explicit label (`unknown user` / `revoked or deleted key`) rather than vanishing — the attribution stays true, only the human name is gone.
      - `type` string, required: `user` or `api_key`.
      - `uuid` string, required
      - `label` string, nullable
  - `total` integer, required: Total runs on this project, across all pages.
  - `cursor_next` string, nullable: Opaque token for the next page — pass it back as `cursor`. Null on the last page.
  - `formula` object, required

## Other responses

- `422`: Validation Error

---

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