Skip to main content
GET
List findings aggregated by source file (review queue rows)

Authorizations

Authorization
string
header
required

An Earnie API key, sent as Authorization: Bearer sk_earnie_.... Create one under Settings > API keys. The key's scopes decide which operations it may call.

Query Parameters

project_id
string<uuid>
required

Project to aggregate findings for.

scan_id
string<uuid>

Same semantics as GET /v1/findings' scan_id: filters to findings last reconciled by this scan run (last_scan_run_id) — or, when a ScanFileScoper is wired, restricts to that scan's file snapshot instead of the volatile last_scan_run_id column.

q
string

Free-text SUBSEQUENCE search — every character of the query must appear, in order and case-insensitively, somewhere across a candidate finding's path, purl, basename, and title. This is the server-side equivalent of the review workspace's client-side fuzzy matcher, not a substring search.

family
enum<string>[]

Restricts aggregation to findings whose normalized family is in this set (oss also covers the underlying deps/provenance domains). A finding whose domain normalizes to none of these still counts toward its row's total/resolved but is excluded when this facet is active. Empty/absent = no family constraint.

Available options:
oss,
crypto,
ai
match_type
enum<string>[]

Restricts aggregation to findings whose derived match type is in this set. Only oss-diff evidence carries a match type ("file" for a whole-file match, "snippet" otherwise); a finding with none is EXCLUDED whenever this facet is active. Empty/absent = no constraint.

Available options:
file,
snippet
finding_state
enum<string>[]

Restricts WHICH FINDINGS are aggregated — a facet applied before grouping — by their state-only bucket (resolved or superseded_by_rule reads as "resolved", everything else as "open"). Deliberately NOT suppressed-aware, unlike the row-level resolved count and the bucket split below: a suppressed-but- open finding still matches finding_state=open even though it counts as resolved on its file's row. Empty/absent = no constraint.

Available options:
open,
resolved
facet
string[]

Restricts aggregation to the findings matching the selection, as family and match_type do: a file appears when at least one of its findings matches, and its counts cover the matching findings. ANDed with family, match_type and finding_state. The same <path>:<value> vocabulary and validation as GET /v1/findings' facet: values of one path are OR-ed, paths are AND-ed, and an unrecognised or non-entitled path is rejected with 400. Like the rest of this endpoint, it never aggregates dependency-sourced crypto findings, which have no local file, so a selection that GET /v1/findings and GET /v1/findings/facets count over dependency crypto matches fewer findings here.

bucket
enum<string>

Selects the queue SECTION after aggregation: open = files with at least one undecided finding; resolved = files where every aggregated finding is decided (state resolved/superseded_by_rule, OR suppressed — see FileQueueItem.resolved). Absent = both sections, no split.

Available options:
open,
resolved
order
enum<string>
default:match_desc

Row order. match_desc (default) sorts by the strongest oss-diff match percent across each row's findings, descending, with path as the stability tiebreaker — the queue's display order, left unchanged by this endpoint's cursor params. path_asc sorts strictly by path — used by the keyboard ⇧j/⇧k "next/previous file with open work" jump, which must walk files in path order regardless of match strength (an earlier implementation that walked match order produced "the random folder jumps bug"). Unknown values fall back to match_desc.

Available options:
match_desc,
path_asc
after_path
string

Returns only rows whose path is strictly greater than this cursor. Paired with order=path_asc and limit=1, yields the row immediately following the cursor — the ⇧j "next file" keyboard jump, as one O(1) request instead of loading every page. Applies after the same filters and bucket as the rest of the query. There is no wraparound: past the last file, the response is empty.

before_path
string

Returns only rows whose path is strictly less than this cursor, and forces the result to be ordered by path DESCENDING regardless of the order param — "immediately preceding" only has a stable meaning in path order, so a caller combining this with order=match_desc still gets a path-ordered answer. Paired with limit=1, yields the row immediately preceding the cursor — the ⇧k "previous file" jump. There is no wraparound: before the first file, the response is empty.

limit
integer
default:50

Rows per page. Out-of-range values fall back to the default of 50.

Required range: 1 <= x <= 200
offset
integer
default:0

Response

Paginated, filter-aware list of file-aggregated queue rows

items
object[]
required
total
integer
required

Rows matching the filters IN THE SELECTED BUCKET — the paging total. Equals counts.files_total when bucket is absent, counts.files_open when bucket=open, counts.files_resolved when bucket=resolved. Deliberately IGNORES after_path/before_path: it is the size of the bucket, not the number of rows remaining past the cursor. The cursor params exist for the single-row keyboard jump, which reads items and never pages, so computing a post-cursor remainder would cost an extra aggregation for a number no caller wants. A future caller that pages FROM a cursor must not read this field as "rows left".

limit
integer
required
offset
integer
required
counts
object
required

Authoritative, filter-aware totals computed over the SAME population items aggregates, before the bucket split — so a single GET /v1/findings/files request can render both the "N to review" and "M resolved" section headers regardless of which bucket was requested.