> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanoss.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List findings aggregated by source file (review queue rows)

> Each item is one source file — the review workspace's Findings queue grouping unit — rolling up every finding that resolves to that file's path after the facet filters below. This replaces the client-side groupByFile rollup, which required eagerly fetching the full findings array: past a few thousand findings that retained hundreds of MiB of browser heap and silently truncated. A file's findings can never straddle a page boundary, so paging the raw finding list cannot back this view — the aggregation has to happen in SQL. `counts` is computed over the SAME filtered population as `items`, before the `bucket` split, so one request renders both the "N to review" and "M resolved" section headers.



## OpenAPI

````yaml /api-reference/earnie/triage-openapi.json get /v1/findings/files
openapi: 3.0.3
info:
  title: Earnie Findings API
  description: List and read findings, triage them, and review resolutions.
  version: 1.0.0
servers:
  - url: https://{tenant}.earnie.dev
    description: Your Earnie deployment
    variables:
      tenant:
        default: your-company
        description: >-
          Your organization's Earnie address, as shown in your browser when you
          sign in.
security:
  - bearerAuth: []
paths:
  /v1/findings/files:
    get:
      tags:
        - findings
      summary: List findings aggregated by source file (review queue rows)
      description: >-
        Each item is one source file — the review workspace's Findings queue
        grouping unit — rolling up every finding that resolves to that file's
        path after the facet filters below. This replaces the client-side
        groupByFile rollup, which required eagerly fetching the full findings
        array: past a few thousand findings that retained hundreds of MiB of
        browser heap and silently truncated. A file's findings can never
        straddle a page boundary, so paging the raw finding list cannot back
        this view — the aggregation has to happen in SQL. `counts` is computed
        over the SAME filtered population as `items`, before the `bucket` split,
        so one request renders both the "N to review" and "M resolved" section
        headers.
      operationId: GetV1FindingsFiles
      parameters:
        - name: project_id
          in: query
          required: true
          description: Project to aggregate findings for.
          schema:
            type: string
            format: uuid
        - name: scan_id
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
            format: uuid
        - name: q
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
        - name: family
          in: query
          required: false
          style: form
          explode: true
          description: >-
            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.
          schema:
            type: array
            items:
              type: string
              enum:
                - oss
                - crypto
                - ai
        - name: match_type
          in: query
          required: false
          style: form
          explode: true
          description: >-
            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.
          schema:
            type: array
            items:
              type: string
              enum:
                - file
                - snippet
        - name: finding_state
          in: query
          required: false
          style: form
          explode: true
          description: >-
            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.
          schema:
            type: array
            items:
              type: string
              enum:
                - open
                - resolved
        - name: facet
          in: query
          required: false
          description: >-
            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.
          schema:
            type: array
            items:
              type: string
          style: form
          explode: true
        - name: bucket
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
            enum:
              - open
              - resolved
        - name: order
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
            enum:
              - match_desc
              - path_asc
            default: match_desc
        - name: after_path
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
        - name: before_path
          in: query
          required: false
          description: >-
            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.
          schema:
            type: string
        - name: limit
          in: query
          required: false
          description: Rows per page. Out-of-range values fall back to the default of 50.
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 200
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            default: 0
      responses:
        '200':
          description: Paginated, filter-aware list of file-aggregated queue rows
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FileQueueListResponse'
        '400':
          description: Invalid query parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Not authenticated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Insufficient permissions (findings:read required)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Internal server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    FileQueueListResponse:
      type: object
      required:
        - items
        - total
        - limit
        - offset
        - counts
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/FileQueueItem'
        total:
          type: integer
          description: >-
            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:
          type: integer
        offset:
          type: integer
        counts:
          $ref: '#/components/schemas/FileQueueCounts'
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
    FileQueueItem:
      type: object
      description: >-
        Aggregated file row returned by GET /v1/findings/files — one row per
        source file, the review workspace Findings queue's grouping unit.
      required:
        - path
        - total
        - resolved
        - families
        - finding_ids
      properties:
        path:
          type: string
          description: >-
            The grouping key: for oss-diff evidence,
            COALESCE(evidence->>'local_file', location_path); otherwise
            location_path; falling back to the finding's title when that
            resolves to empty (mirrors groupByFile's `findingPath(f) ||
            f.title`).
        total:
          type: integer
          description: >-
            Number of findings aggregated into this row, after the facet
            filters.
        resolved:
          type: integer
          description: >-
            Of `total`, how many are decided: state resolved or
            superseded_by_rule, OR suppressed (a "mark original" finding needs
            no further review even while its raw state stays open —
            findingView.ts#findingIsResolved). A row belongs to the
            `bucket=resolved` section iff resolved == total.
        top_match:
          type: number
          nullable: true
          description: >-
            Strongest oss-diff match percent across the row's findings; null
            when none carry one. Rows with no match percent sort as though it
            were 0 under the default order.
        families:
          type: array
          items:
            $ref: '#/components/schemas/FileQueueFamilyTag'
          description: One entry per distinct normalized family present in the row.
        finding_ids:
          type: array
          items:
            type: string
            format: uuid
          description: >-
            A bounded, deterministically ordered (by id) sample of at most 50 of
            the row's finding IDs. This feeds per-row policy-verdict badges,
            which only need "does this file have any", not the complete set — it
            is NOT the authoritative finding list for the file (opening a file
            fetches its findings directly by path).
    FileQueueCounts:
      type: object
      description: >-
        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.
      required:
        - files_total
        - files_open
        - files_resolved
        - findings_total
        - findings_resolved
      properties:
        files_total:
          type: integer
          description: Total files matching the query's filters (both sections combined).
        files_open:
          type: integer
          description: Of files_total, how many have at least one undecided finding.
        files_resolved:
          type: integer
          description: Of files_total, how many have every aggregated finding decided.
        findings_total:
          type: integer
          description: Total findings aggregated across every matching file.
        findings_resolved:
          type: integer
          description: Of findings_total, how many are decided.
    FileQueueFamilyTag:
      type: object
      description: >-
        A finding whose domain does not normalize to oss/crypto/ai contributes
        to its row's total/resolved counts but produces no tag here.
      required:
        - domain
        - count
        - all_resolved
      properties:
        domain:
          type: string
          enum:
            - oss
            - crypto
            - ai
          description: Normalized family (oss also covers the deps/provenance domains).
        count:
          type: integer
          description: Number of this family's findings aggregated into the row.
        all_resolved:
          type: boolean
          description: >-
            True when every one of this family's findings in the row is decided
            (state resolved/superseded_by_rule, or suppressed) — drives the
            family glyph's dimmed state.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        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.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.