neosourceDocs
Search docs

List CI checks for a PR's head commit

GET/api/repos/{owner}/{repo}/pulls/{number}/checks

listPullRequestChecks

Derives one check per job of the workflow runs whose trigger commit equals the PR's current head — runs for a previous (stale) head are never counted. One check per context: where several runs report the same context (a re-run attempt, a dispatch on top of a `pull_request` run) the NEWEST run's verdict wins, matching the merge gate. There is no stored checks state; this is a pure projection of the CI runs.

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/repos/OWNER/REPO/pulls/NUMBER/checks'

fetch

fetch("https://neosource.dev/api/repos/OWNER/REPO/pulls/NUMBER/checks", {
  method: "GET",
});

Path parameters

ownerrequired

Repository owner or organization slug.

string

reporequired

Repository name.

string

numberrequired

Change request number.

integerint32

Responses

200Derived checks + summary

application/json

PrChecksResponse

object

`GET /api/repos/{owner}/{repo}/pulls/{number}/checks`.

annotation_counts

AnnotationCounts

object

Per-level counts for one run's (or a PR's aggregate) annotations — the check-row badge (`3 ✕ / 5 ⚠`). Thin wire mirror of the core `AnnotationCounts` (core stays free of serde/utoipa derives).

failurerequired

integerint64

noticerequired

integerint64

warningrequired

integerint64

blocked_by

array

Human-readable reasons the merge is blocked (empty when mergeable or ungated). Deliberately open vocabulary — render verbatim.

items

string

can_bypass

boolean

Viewer-scoped: this account could merge despite `blocked_by`. `true` only when a bypass would actually DO something — the branch is protected, the PR is not mergeable, the rule sets `enforce_admins: false`, and the viewer holds repo Admin. In particular it stays `false` on an already-mergeable PR, so the UI never offers a "merge without waiting for requirements" button when there is nothing to wait for.

checksrequired

array

One check per `context`, newest run first. Several runs can report the same context on one commit (a re-run attempt, a manual dispatch on top of a `pull_request` run); only the newest one's verdict is served, which is the same collapse the merge gate judges.

items

CheckRun

object

One CI check on a PR's head commit: the projection of one job of one workflow run (later also one external commit status).

contextrequired

string

Stable identity of the check: `"<workflow name> / <job key>"`, matrix legs append their values. Branch protection (P2.3) matches required checks against this string.

description

string | null

Human-readable detail (held-for-approval, skipped, …); P2.5 fills per-run test summaries in here.

job_keyrequired

string

quarantined

boolean

`true` when this check is red (`failure`/`error`) but was soft-failed out of the merge gate's `blocked_by` because every one of its failing tests is quarantined (P3.3 of `plans/ci-merge-gating-polish-2026-07.md`). The check still reports its real (red) `state` — this only badges it "quarantined (non-blocking)". Always `false` when merge gating isn't wired, the check is green/pending, or the red isn't quarantine-eligible.

run_idrequired

string

Workflow run the check derives from (UUID).

skipped

boolean

`true` when the job never ran — gated off by its job-level `if:`, or skipped because a `needs:` predecessor did not succeed. The `state` stays `success` (GitHub scores a skipped check as passing, so it must not block a merge on a required check that was never meant to run on this ref); this flag is what lets the UI render "Skipped" rather than a green tick implying work happened.

staterequired
CheckState
target_urlrequired

string

App path of the run page (`/{owner}/{repo}/actions/runs/{run_id}`).

checks_summaryrequired

ChecksSummary

object

Roll-up of the check states for the merge box. Counted over the newest-run-per-context set (`neosource_forge::head_checks`), so a run that a re-run superseded is not a second check.

failedrequired

integerint32

`failure` + `error` states — both render red and both block.

pendingrequired

integerint32

skipped

integerint32

Checks whose job never ran (gated off by a job-level `if:`, or a `needs:` predecessor did not succeed). Counted separately from `success`; `success + failed + pending + skipped == total`.

successrequired

integerint32

Checks that ran and passed. Deliberately EXCLUDES skipped ones: a skipped check carries `state: success` so it cannot block a merge, but counting it as "passed" would tell the reader that work happened when it did not — the same claim the per-check `skipped` flag exists to stop the UI making.

totalrequired

integerint32

coverage
one of
  • null

  • CoverageSummary

    object

    Coverage summary for the PR's head-commit run, folded onto `PrChecksResponse.coverage`. Wire mirror of the core `RunCoverageSummary`, with the percentages pre-computed (core stays free of serde/utoipa derives, and the SPA shouldn't hand-roll the divide).

    diff_lines_coveredrequired

    integerint32

    diff_lines_pct

    number | nulldouble

    `diff_lines_covered / diff_lines_total * 100.0` — `None` when `diff_lines_total == 0` (nothing added/changed under coverage, or diff-coverage couldn't be computed for this run).

    diff_lines_totalrequired

    integerint32

    lines_pctrequired

    numberdouble

    `lines_covered / lines_total * 100.0` — `0.0` when `lines_total == 0`.

enforce_admins

boolean

Echoed from the matching branch-protection rule so the client can explain WHY a bypass is or isn't offered. `false` when the target branch is unprotected (or gating isn't wired) — there is then no gate to enforce against anyone.

head_commit

string | null

PR head commit the checks are derived against (40-char Git OID hex, same form as `PullRequestResponse.head_commit`). `null` for a PR whose source branch has no resolved tip yet — `checks` is then always empty.

mergeable

boolean | null

Merge-gate verdict (P2.4): `true`/`false` when merge gating is wired server-side, `null` when it isn't. `false` iff `blocked_by` is non-empty. An unprotected target branch reports `true`.

up_to_date

boolean | null

Is the head already up to date with the target branch? `Some(false)` = the head is behind the target and can be updated — this is what renders the "Update branch" button. `None` = not knowable (the source branch has no pushed commits, or the target branch has no resolvable tip). Independent of `mergeable`/`blocked_by`: computed whether or not merge gating is wired and whether or not a protection rule matches, because being behind is worth acting on even when nothing gates on it.

Standard errors

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

  • 400Bad Request — one of: invalid_input
  • 403Forbidden — one of: forbidden
  • 404Not Found — one of: not_found
  • 423Locked — one of: busy
  • 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.

CheckState

string

Wire-level state of one check. Deliberately the four-value GitHub commit-status vocabulary (`pending`/`success`/`failure`/`error`) so run-derived checks and externally-posted statuses (P2.2) aggregate in one space.

"pending""success""failure""error"