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

# Findings and Detections

> Which endpoints describe a vulnerability itself, and which describe one scan's report of it

The API describes vulnerabilities at two levels.

* A **detection** is one occurrence of a vulnerability in one scan, as that scan reported it: the file and line, the package and CVE, or the IaC rule. Every scan produces new detections with new ids. The endpoints listed under **Issues** return detections; an *issue* and a *detection* are the same thing.
* A **finding** is the vulnerability itself, in one project, across every scan that detected it. It has one id that does not change between scans, and it is where triage lives: status, comment, assignee, risk-acceptance expiry, due date and SLA.

> [!NOTE]
>
> **`/detections` is the same API as `/issues`, under a new name.** Every endpoint under **Issues** is also served under `/detections`, with the same code, parameters and response. We call it `/detections` because "issue" was easily confused with a finding: each result is one scan's detection of a vulnerability, not the vulnerability itself. Use `/detections` in new integrations. `/issues` stays supported, so existing integrations need no change.

![Three scans, each with its own detection, all pointing at one finding that holds the triage decision](https://mintcdn.com/corgea/iJfCXTeBC6cZmqD9/images/api/findings-detections-light.svg?fit=max\&auto=format\&n=iJfCXTeBC6cZmqD9\&q=85\&s=45045a31aa91145cc9848dd8a475a5c4)

| | Finding | Detection (issue) |
| - | - | - |
| What it is | One vulnerability in one project | One occurrence of it in one scan |
| Id | Same UUID across rescans, branches and fingerprint migrations | New id in every scan |
| Triage | Lives here | Mirrors its finding |
| Use it for | Ticket sync, bulk triage, metrics, "is this still open?" | Code, line numbers, fixes, per-scan reports |
| Link to the other | `latest_detection.id` | `finding_id` |

## Moving between the two

Every detection item carries the `finding_id` of its finding. Group detections by it, never by `first_detection_date`, which is a property of the finding rather than a key. The field is `null` only for detections ingested before findings existed.

Every finding carries `latest_detection`: its newest detection, with the id the detection endpoints take and the family's own locator (`file_path` and `line_number`, `path`, `package`, `version` and `cve`, or `path`, `rule_id`, `start_line` and `end_line`).

A triage change made on either level lands on the finding, and so on every one of its detections.

## Which endpoint to call

| You want to | Call |
| - | - |
| Everything a scan reported, with code locations | `GET /api/v1/scan/{scan_id}/detections` and the SCA, container and IaC variants |
| The explanation and fix for one occurrence | `GET /api/v1/detections/{issue_id}` |
| One row per vulnerability, filtered by project, status or urgency | `GET /api/v1/findings` |
| What changed since your last sync | `GET /api/v1/findings?status_changed_since=…` |
| Vulnerabilities whose detections were deleted with a scan or project | `GET /api/v1/findings?detection_state=retired` |
| The triage state of one vulnerability | `GET /api/v1/findings/{finding_id}` |
| Change status for a set of vulnerabilities | `PATCH /api/v1/triage/findings` with `finding_ids` or a `rule` |
| Change status through one detection; the change lands on its finding | `PATCH /api/v1/detections/{issue_id}` |

## The detections endpoints

`/detections` is the new name for the endpoints under **Issues**, not a separate API. Each of them answers under both paths:

| Issues path | Detections path |
| - | - |
| `/api/v1/issues` | `/api/v1/detections` |
| `/api/v1/issue/{issue_id}` | `/api/v1/detections/{issue_id}` |
| `/api/v1/issues/sca`, `/api/v1/issues/sca/{issue_id}` | `/api/v1/detections/sca`, `/api/v1/detections/sca/{issue_id}` |
| `/api/v1/issues/container`, `/api/v1/issues/iac` | `/api/v1/detections/container`, `/api/v1/detections/iac` |
| `/api/v1/scan/{scan_id}/issues` and its variants | `/api/v1/scan/{scan_id}/detections` and its variants |

Both paths run the same code and return the same response, including the `issues` and `total_issues` keys. Use `/detections` in new integrations; `/issues` stays supported.

## The findings endpoints

`GET /api/v1/findings` returns one row per finding, newest first by when each was first detected, paginated with `page` and `page_size` (up to 100) and a `total_findings` count. Filters take comma-separated lists unless noted:

| Parameter | Values |
| - | - |
| `issue_type` | `sast`, `secret`, `logic_auth`, `code_quality`, `dependency`, `container`, `iac`. Default: every security family the token may view (all but `code_quality`) |
| `status` | `open`, `fix_in_progress`, `accepted_risk`, `false_positive`, `fixed`, `duplicate` |
| `urgency` | `CR`, `HI`, `ME`, `LO` |
| `project_id` | Project ids |
| `project` | One project name |
| `repo` | Substring of the repository URL |
| `cwe` | `CWE-79` or `79` |
| `detection_state` | `active` (default), `retired`, `all` |
| `status_changed_since` | ISO 8601 datetime, or a date read as midnight UTC: findings whose triage changed after this instant |
| `created_since` | ISO 8601 datetime, or a date read as midnight UTC: findings first detected after this instant |
| `assigned_to` | One user id |
| `sort_by` | `-created_at` (default), `created_at`, `urgency`, `-urgency`, `status`, `-status`, `status_changed_at`, `-status_changed_at`. `created_at` is when the finding was first detected, the instant `created_since` filters on. It is not in the response, and equals `first_detection_date` unless the finding's oldest detections were deleted with their scan |

There is no `branch` filter: a finding belongs to a project, and the branch is a property of its detections. The token needs the same view permission as the family's detections list; naming a family it may not view is a `403`.

`GET /api/v1/findings/{finding_id}` returns one finding with the same shape: its triage fields, `urgency`, `classification`, `project`, `detection_state`, `first_detection_date`, `last_detection_date`, `detections_count` and `latest_detection`.

### `detection_state`

A finding is `active` while it has a detection in its project, and `retired` once its last detection has been deleted together with its scan or project. A finding that a rescan no longer reports is not retired: it stays `active` and its `status` becomes `fixed`. A retired finding keeps its triage, so a detection that comes back is attached to the same finding id and inherits the same decision.

> [!NOTE]
>
> `detection_state` is `null`, and `GET /api/v1/findings` answers `404`, until the account's finding index is complete. Detection endpoints and `GET /api/v1/findings/{finding_id}` are not affected.

## Triage by finding

`PATCH /api/v1/triage/findings` accepts `finding_ids`: a list of finding ids (from `GET /api/v1/findings` or the `finding_id` on detection items) or detection ids. A finding id stands for the finding's newest detection, and the decision reaches every detection of the finding. Ids that resolve to nothing come back in `unresolved_ids` and count as unmatched, so a batch with a stale id still applies the rest. Bulk triage covers the SAST, secret, logic, code-quality and dependency families; a container or IaC id comes back in `unresolved_ids`, and the audit rows report `finding_type` as `sast` or `sca`.

A `rule` can carry a `statuses` scope, so one call can, for example, move every finding that is accepted risk today to false positive.

## A sync loop

Key each ticket on the finding id. A detection id changes with every scan; a finding id does not.

Each run reads three lists and writes one batch. Every list is paginated: request `page=1, 2, …` with `page_size=100` until `page` reaches `total_pages`. Save the time the run started, as a full ISO 8601 datetime in UTC, and pass it as `<last run>` next time.

1. `GET /api/v1/findings?created_since=<last run>`: open a ticket for each new finding. A new finding has no triage change yet, so step 2 does not return it.
2. `GET /api/v1/findings?status_changed_since=<last run>&detection_state=all`: apply triage changes, including those on retired findings. A finding whose `status` became `fixed` closes its ticket.
3. `GET /api/v1/findings?detection_state=retired`: reconcile retired findings. Retirement records no time and changes no status, so neither cursor returns it. Compare this full list with your open tickets, and close or flag the ones that appear. A detection that comes back makes the finding `active` again, with the same id.
4. `PATCH /api/v1/triage/findings` with `finding_ids`: push decisions made in the ticket system back to Corgea. Read `unresolved_ids` even on a `200`. Those ids, including every container and IaC finding, were not updated, while the rest of the batch was.

Add `issue_type` to each list call if you sync code-quality findings, which the default leaves out. A `404` from `GET /api/v1/findings` means the account's finding index is not complete yet, not that there is nothing to sync. Retry later and keep the old cursor.


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