> ## Documentation Index
> Fetch the complete documentation index at: https://docs.entire.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Report checks

> Write check results on a commit, update them as work progresses, and read them back.

A plugin with `checks:write` can report results on a commit in a repository the installation covers. Use checks for tests, security scans, code review or any other work whose result a customer may require before merging.

## What you need

* Register your plugin with `checks:write` and have the customer accept it. Writing checks alone does not need contents access, but add `contents:read` (or `contents:write`) if your service reads code or receives ref or trail webhooks.
* [Mint an access token](https://docs.entire.com/guides/api/plugins/act-for-an-installation.md#mint-an-access-token) scoped to the repository, with `{"checks": "write"}`.
* Take the repository's `id` and processing-primary `api_base_url` from the [repository list](https://docs.entire.com/guides/api/plugins/act-for-an-installation.md#list-repositories). Use the full commit SHA, 40 or 64 characters.

## Report a result

A **suite** groups one kind of work, such as a security scan. You can report its result alone, or include individual **runs** for its parts. Send one suite per request:

```http theme={null}
POST {api_base_url}/api/v1/repos/{repo_id}/commits/{sha}/check-runs
Authorization: Bearer {access token}
Content-Type: application/json

{
  "branch": "main",
  "check_suite": {
    "key": "security",
    "name": "Security scan",
    "external_id": "scan-1842",
    "details_url": "https://scanner.example/scans/1842",
    "status": "completed",
    "conclusion": "success",
    "external_updated_at": "2026-10-27T00:03:51.004Z"
  },
  "check_runs": []
}
```

`branch` is the branch name without `refs/heads/`. Keep `key` stable and readable: customers see it on the trail and use it when they [require checks](https://docs.entire.com/guides/ci/checks.md). The suite's `name` is stored metadata, not its current display label. Give `external_id` a new value for each attempt, including a rerun on the same commit. It can be up to 255 bytes.

`details_url` is optional. It must use HTTPS and contain no credentials. Link to the result in your service, where the customer can read logs or take action.

A write applies in full or not at all. Retrying the same request is safe. A `200` response identifies the suite and returns an outcome for each run: `created`, `updated`, `unchanged` or `ignored_stale`. It does not return the suite's state; [read it back](#read-your-results) to confirm that.

## Keep it current

Report `queued` when you accept work, `in_progress` when it starts, and `completed` when it ends. Use these values for both suites and runs:

| State | `status` | `conclusion` |
| :- | :- | :- |
| Waiting to start | `queued` | Omit |
| Running or cancelling | `in_progress` | Omit |
| Passed | `completed` | `success` |
| Failed | `completed` | `failure` |
| Allowed to fail | `completed` | `neutral` |
| Cancelled | `completed` | `cancelled` |
| Skipped | `completed` | `skipped` |
| Timed out | `completed` | `timed_out` |
| Needs a person's attention, without blocking merge | `completed` | `action_required` |

`action_required` counts as passing. If an approval must block a merge, keep the suite pending instead.

When you send suite state, include both `status` and `external_updated_at`, plus `conclusion` when completed. Set the timestamp to when the state changed in your service, not when you sent the request. Timestamps must be RFC 3339 with a zero UTC offset; nonzero offsets are refused with `400`. Entire stores millisecond precision.

Suites and runs can also carry `started_at` and `completed_at`. The same rules apply to them, and `completed_at` can't be before `started_at`.

Older state updates are ignored. An identical retry leaves the result unchanged. An update with the same timestamp can change values, but reopening completed work as `queued` or `in_progress` needs a newer timestamp. A timestamp more than 60 seconds in the future is refused.

Within an attempt, cancellation cannot replace a passing result. Across suite attempts, a cancelled attempt does not replace the newest non-cancelled attempt if that attempt passes. Keep the same `external_id` when updating an attempt; use a new one when you start the work again.

## Include individual runs

You don't need a run for every internal step. When separate results help the customer, add them to `check_runs`:

```json theme={null}
{
  "key": "dependencies",
  "name": "Dependency scan",
  "external_id": "scan-1842/dependencies",
  "status": "completed",
  "conclusion": "failure",
  "external_updated_at": "2026-10-27T00:03:51.004Z",
  "details_url": "https://scanner.example/scans/1842/dependencies",
  "output": {
    "title": "Vulnerable dependency found",
    "summary": "See the scan for affected packages and upgrade instructions."
  }
}
```

Each run has its own stable `key` and attempt `external_id`. Two runs in one request can't share both. A request can include up to 5,000 runs. An output title can be up to 255 characters and its Markdown summary up to 65,535 bytes. Keep reports small and link to full logs.

The suite's reported state decides its result, even when runs have different results. Keep it pending while more work can start, and report its final result when the work ends. This also lets you report a failure before any run starts.

To update runs without changing suite state, omit all suite state fields. Runs omitted from a request keep their stored results. A suite that has never reported its own state takes its result from its current runs; it must have at least one run. Don't create a run with the suite's key just to represent the suite's result.

### Deadlines

A run can include `deadline_at`, in UTC and at most 24 hours ahead. After that time, the expiry sweep completes an `in_progress` run as `timed_out`. Queued runs and runs without a deadline don't time out automatically. Omitting a deadline from a later nonterminal update preserves it; completing the run clears it.

Suites have no deadline. A run timing out doesn't change a suite that reports its own state, so report the suite's final result yourself.

## Read your results

```http theme={null}
GET {api_base_url}/api/v1/repos/{repo_id}/commits/{sha}/check-suites
Authorization: Bearer {access token}
```

```http theme={null}
GET {api_base_url}/api/v1/repos/{repo_id}/commits/{sha}/check-runs?suite_key=security
Authorization: Bearer {access token}
```

Use the same `checks:write` token. These endpoints return your plugin's current results on the commit, without earlier attempts, in one page. Omit `suite_key` to read runs from all your suites. Run attempts are ordered by their update time; a suite attempt's ordering also accounts for updates to its runs.

Each check-writing plugin has its own gate. See [the plugin gate rules](https://docs.entire.com/guides/api/plugins/go-sdk.md#your-plugins-gate) for how your reports affect merging, and [Review and require checks](https://docs.entire.com/guides/ci/checks.md) for the customer's settings. If the gate has no required names, it doesn't wait for a suite that hasn't reported yet. Send `queued` promptly if your work should block merging while it runs.

Mirrored repositories use the same API. Entire shows your results on its trails; it does not create a separate upstream check for each suite.

## Errors

| Status and code | What to do |
| :- | :- |
| `400 invalid_request` | Fix the value described in `detail`, such as a missing `key`, `name` or `external_id`, incomplete suite state, a timestamp not in UTC or too far ahead, or a duplicate run. |
| `422 validation_error` | Fix the JSON: field types or timestamp syntax. |
| `403 forbidden` | Check that the token has `checks:write` for this repository. |
| `409 wrong_cell` | Refresh the repository list and retry at its processing-primary `api_base_url`. |
| `409 conflict` | Check that the installation still covers the repository. After a reinstall, use new attempt IDs; another installation cannot overwrite an existing attempt. |
| `503` | Retry with backoff. The write may have succeeded even if Entire couldn't confirm it. |

For token errors, see [Access repositories](https://docs.entire.com/guides/api/plugins/act-for-an-installation.md#errors). For a Go implementation, see [Report check runs with the SDK](https://docs.entire.com/guides/api/plugins/go-sdk.md#report-check-runs).
