Integrations

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:

json
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:

json
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.
  • name is what people see and what a ruleset requires. Keep it stable across commits.
  • status is queued, in_progress or completed. A completed check needs a conclusion: success, failure, neutral, cancelled, skipped, timed_out or action_required. success, neutral and skipped pass 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.
  • detailsUrl is an HTTPS link to your results; externalId is your own ID for the run, sent back with re-run requests.
  • output has a title (255 characters) and a Markdown summary and text (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. startedAt and completedAt default to when GitCafe first saw the check running and completed. Only rerequestable keeps 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:

outcomeMeaning
createdA new check.
updatedThe check now has what you sent.
unchangedYou sent what it already had; nothing changed.
ignored_staleYour 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:

json
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:

  1. shows the check as queued, so a required check blocks merging, and
  2. sends check.rerequested to your app's installation, with the check's id, key, headOid, name, externalId, detailsUrl and earlier conclusion.

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.

Read as MarkdownGitCafe documentation
Documentation