Report checks from your CI
An app or a script with an access token can report CI results on a commit as checks. A reported check shows on the commit's page and in a pull request's checks panel next to GitCafe's own checks, and a ruleset can require it by name. Commit statuses still work; checks add a stable identity, a summary, annotations and re-runs. The operations are under CI in the REST API reference.
Permissions
Writing checks needs repo:checks:write; reading one back needs
repo:checks:read. Neither reaches code: an app with only
repo:checks:write cannot push or post commit statuses. An app asks for the
permission in its manifest's permissions.autonomous.scopes:
| 1 | { "kind": "repo", "family": "checks", "level": "write" } |
A personal access token that can write code can also write checks, and a person still needs write access to the repository. An app's bot needs no role: the permission and the repositories the installation selected are enough.
Report a check
Use Report a check run with the commit's SHA and a key of your choice,
build here, and the check's whole state as the body:
| 1 | { |
| 2 | "name": "depot/build", |
| 3 | "status": "completed", |
| 4 | "conclusion": "failure", |
| 5 | "detailsUrl": "https://depot.example/builds/7", |
| 6 | "externalId": "build-7", |
| 7 | "reportedAt": "2026-10-23T12:05:00Z", |
| 8 | "output": { "title": "1 test failed", "summary": "`test/a.ts` failed." }, |
| 9 | "annotations": [ |
| 10 | { |
| 11 | "path": "test/a.ts", |
| 12 | "startLine": 4, |
| 13 | "endLine": 4, |
| 14 | "level": "failure", |
| 15 | "message": "expected 1, got 2" |
| 16 | } |
| 17 | ], |
| 18 | "rerequestable": true |
| 19 | } |
- The commit is the full 40-character SHA, and the repository must have it.
- The key (
build) identifies the check for you: up to 100 letters, digits,.,_,:or-, starting with a letter or digit. Writing the same key on the same commit again updates that check; another app's checks never collide with yours. nameis what people see and what a ruleset requires. Keep it stable across commits.statusisqueued,in_progressorcompleted. A completed check needs aconclusion:success,failure,neutral,cancelled,skipped,timed_outoraction_required.success,neutralandskippedpass a required check; the rest fail it.suite(optional) groups checks: your checks on a commit with the same suite key, in the key's format, show together under it in the checks panel, with the suite's worst state. Another reporter's suite with the same key is a separate group. A suite only groups; rulesets still require checks by name.detailsUrlis an HTTPS link to your results;externalIdis your own ID for the run, sent back with re-run requests.outputhas atitle(255 characters) and a Markdownsummaryandtext(65,535 characters each). Up to 50 annotations point at lines of files; columns are allowed on a single line.- Each write replaces the whole check: leave out a field and it is cleared.
startedAtandcompletedAtdefault to when GitCafe first saw the check running and completed. Onlyrerequestablekeeps its value when you leave it out.
The response is 201 when it created the check and 200 otherwise, with the
stored check and an outcome:
outcome | Meaning |
|---|---|
created | A new check. |
updated | The check now has what you sent. |
unchanged | You sent what it already had; nothing changed. |
ignored_stale | Your reportedAt is older than the stored one; nothing changed. |
Send reportedAt (when your system observed the state) so retries and
out-of-order deliveries cannot overwrite newer results. Without it, GitCafe
uses the time it receives the write. At the same reportedAt, a completed
check is not reopened. Times more than a minute in the future are refused.
Deadlines
A check that stays queued or in_progress does not block a pull request
forever. Six hours after your last write that left it unfinished, GitCafe
concludes it timed_out, which fails a required check. Set
timeoutMinutes (1 to 4320, three days) on an unfinished write to choose
another bound. Each such write starts the bound again, so a long job stays
alive by reporting its state again before the deadline, even unchanged. The
stored check shows the current deadlineAt.
A timed-out check keeps its key. A later write from you, with a newer
reportedAt, replaces it as usual. A re-run request your app does not answer
within six hours also concludes timed_out.
GitCafe sends your app nothing when a check times out. Your app sees the
timed_out conclusion in the response to its next write for that key, or by
reading the check back.
From a shell or CI job, cafe check report <key> --commit <sha> --name <name> --status <status> reports a check and cafe check list shows every check on a
commit (CLI).
Report many checks at once
Report check runs in a batch writes up to 50 of your checks on one commit in one request. Send each check's key and state:
| 1 | { |
| 2 | "items": [ |
| 3 | { "key": "unit", "check": { "name": "depot/unit", "status": "queued" } }, |
| 4 | { "key": "lint", "check": { "name": "depot/lint", "status": "queued" } } |
| 5 | ] |
| 6 | } |
Each item is written as a single report would be, and the response lists one
outcome per item in the same order. An item GitCafe declines, such as one
with a time in the future, comes back as refused with an error, and the
others are still written. A malformed body, or a key that appears twice,
refuses the whole batch. So does a batch over 500 annotations, or over 1 MiB of
output and annotation text (paths included), in all: it fails with check-batch-too-large and
writes nothing, so split it.
Get a reported check run reads a check back with its annotations, and List checks for a commit lists every check on a commit, from any source.
Require a check
Add the check's name to a ruleset's Require status checks rule. A pull request
can merge only when every required name passes on its head commit. Commit
statuses and checks with the same name count together, and a failure from any
of them fails the requirement. Anyone who may write checks or statuses can
report under any name.
To accept a name only from your app, pin it: list it under the rule's checks
with your app's ID, as in {"context": "ci/build", "appId": "int_…"}. Then
only a check your app's installation reported through this API satisfies it;
a commit status, a person's check or another app's check with the same name
does not. On the ruleset page, under Checks pinned to an app, choose
Add pinned check, enter the name and choose the app from the apps installed
in the organization; organization admins see that list. The rulesets API and
cafe repo ruleset create|edit --rule take the same checks entries.
Only reports from the app's current installation count for a pin. After you uninstall and reinstall the app, its new installation reports again and replaces whatever the old one left on the commit.
Re-run a check
When a check is completed and your app reported it with
"rerequestable": true, the commit and pull request show Re-run next to
it. People with write access to the repository can choose it, or use Request a
re-run of a check. Only people can: an app's bot, your own included, cannot
request a re-run. GitCafe then:
- shows the check as queued, so a required check blocks merging, and
- sends
check.rerequestedto your app's installation, with the check'sid,key,headOid,name,externalId,detailsUrland earlierconclusion.
Answer by writing the same key on the same commit again, usually queued
then completed. Writes with a reportedAt from before the request do not
answer it. Asking again while a request is open sends nothing more. Both
happen or neither does: a re-run is refused, and the check left as it was,
when your app could not receive check.rerequested for this repository. That
is when the app is paused, suspended or uninstalled, the repository is no
longer selected, the permission is gone, or no webhook endpoint subscribes to
the event.
Only apps can offer re-runs: subscribe to check.rerequested in the manifest's
capabilities.webhooks.events, which needs a repo:checks permission. If your
app cannot re-run a check, leave rerequestable out or set it to false.