Skip to main content
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.
/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 decisionThree scans, each with its own detection, all pointing at one finding that holds the triage decision

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

The detections endpoints

/detections is the new name for the endpoints under Issues, not a separate API. Each of them answers under both paths: 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: 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.
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.