neosourceDocs
Search docs

List a workflow run's CI annotations

GET/api/runs/{run_id}/annotations

listRunAnnotations

SARIF/coverage findings (file+line, severity) attached to a workflow run by the P1.1 ingest path (plans/ci-merge-gating-polish-2026-07.md). Informational — annotations never affect mergeability on their own. Returns at most `TOTAL_ANNOTATIONS_MAX` findings; `truncated` is true only when more existed at ingest (detected by a cap+1 extra-row probe, so a run with exactly the cap is not flagged).

Authentication is not described for this operation in the spec — that does not mean it is public. Check tokens and scopes.

curl

curl -X GET 'https://neosource.dev/api/runs/RUN_ID/annotations'

fetch

fetch("https://neosource.dev/api/runs/RUN_ID/annotations", {
  method: "GET",
});

Path parameters

run_idrequired

string

Responses

200Annotations

application/json

RunAnnotationsResponse

object

`GET /api/runs/{run_id}/annotations`. The full findings list for one run — loaded lazily when a check row with a non-zero `PrChecksResponse.annotation_counts` is expanded.

annotationsrequired

array

items

CheckAnnotation

object

One SARIF/coverage finding attached to a workflow run. No `job_key` on the wire — the ingest path can't always attribute a finding to a specific job (deferred, see the plan's P1.1 note), and annotations are scoped to the run, not a check.

end_line

integer | nullint32

levelrequired
AnnotationLevel
messagerequired

string

pathrequired

string

Repo-relative path the finding attaches to.

rule_id

string | null

SARIF `ruleId`, when `source` is `sarif`.

sourcerequired
AnnotationSource
start_linerequired

integerint32

title

string | null

totalrequired

integerint64

Number of annotations returned in this response (== `annotations.len()`, so never more than `TOTAL_ANNOTATIONS_MAX`).

truncatedrequired

boolean

`true` when more findings existed at ingest than are returned here — ingest sorts failure>warning>notice, keeps the top `TOTAL_ANNOTATIONS_MAX` (plus one sentinel row), and the read path detects the sentinel with a `cap + 1` fetch. A run with *exactly* the cap reports `false` (no sentinel), fixing the old `len == cap` false positive.

403Forbidden — one of: forbidden, needs_scope

application/json

one of
  • ErrorForbidden
  • NeedsScopeError

    object

    `403` body returned when listing private repos but the linked identity lacks the required provider scope. The SPA turns this into an incremental-authorization prompt (Tier 2) that calls the `/elevate` OAuth endpoint with this `scope`.

    errorrequired

    string

    Always `needs_scope` — this body exists to carry the extra fields that kind needs.

    "needs_scope"

    scoperequired

    string

    The provider scope to request via elevation (e.g. `"repo"`).

Standard errors

Bodies documented once for the whole API — see standard errors.

  • 400Bad Request — one of: invalid_input
  • 404Not Found — one of: not_found
  • 429Rate limited — retry after the `Retry-After` header
  • 500Internal server error
  • 503Service temporarily unavailable / at capacity — retry after the `Retry-After` header
  • 504Gateway timeout — the request exceeded the server's handling budget

Schemas

Referenced above. Listed here rather than expanded inline, so the same definition is not repeated at every level.

AnnotationLevel

string

Severity of one CI annotation (SARIF/coverage finding). Wire vocabulary mirrors GitHub Actions' annotation levels. Informational — annotations never affect mergeability by themselves.

"failure""warning""notice"

AnnotationSource

string

What produced a [`CiAnnotation`]. Both sources ride the existing artifact-upload convention (no new workflow-YAML key) — the ingest path tells them apart by artifact name (`is_sarif_artifact`/`is_coverage_artifact`, P1.1/P2.1).

"sarif""coverage"