# API: Checks

Check what changed between two versions.

## Check a connected GitHub pull request

`POST /v1/github/pull-request-checks` requests a live check using the workspace's GitHub App installation:

```json
{"repository":"acme/shop","pull_request":7}
```

Requires a key tied to an active workspace member who can run checks. Machine keys are refused. The workspace and actor come from the authenticated key; request fields cannot select another workspace or user. The GitHub installation must be linked to that workspace and grant repository access.

A `200` response contains `status` (`completed`, `waiting`, or `error`), `message`, `repository`, `pull_request`, `head_sha`, `base_sha`, and, when available, `check_id`, `gate_id`, `outcome`, `details_url`, `github_check_id` and `github_check_url`. Inspect `status` and `outcome`: HTTP success does not mean the release passed. Waiting for CI saves no gate decision; retry explicitly when CI finishes. Publication errors can include a saved decision link.

This runs the full release gate, saves the decision and publishes the GitHub check. It doesn't post a pull request comment.

## Changelog & semver

These three endpoints take the same [request](#request): commit messages in, a version bump and changelog out.

### POST /v1/analyze

Returns a version recommendation and a changelog. A successful call also saves the release and fires your [outbound notifications](/docs/api-integrations#outbound-notifications).

### POST /v1/changelog

Returns the changelog only. Use it if you handle versioning separately.

### POST /v1/semver

Returns the version recommendation only. Use it if you generate changelogs elsewhere.

### Request

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `commits` | array | Yes | 1 to 250 commit objects |
| `commits[].message` | string | Yes | The commit message. Can't be empty. |
| `commits[].author` | string | No | Commit author |
| `commits[].timestamp` | string | No | ISO 8601. Used for the changelog date. |
| `commits[].hash` | string | No | Short or full hash. Used for commit links when `repository` is set. |
| `previous_version` | string | No | Current version, semver (`1.4.2`). Defaults to `0.0.0` with a warning. |
| `repository.url` | string | With `repository` | Repository URL |
| `repository.provider` | string | No | `github`, `gitlab` or `bitbucket`. Detected from the URL when omitted. |
| `repository.branch` | string | No | Branch name, passed to Linear release notifications |
| `options.format` | string | No | `markdown` (default), `json`, `keepachangelog` or `slack` |
| `options.include_ignored` | boolean | No | Include uncategorized commits in the output. Default `false`. |

```json
{
  "commits": [
    { "message": "feat: add dark mode toggle", "author": "paul", "timestamp": "2026-05-20T10:00:00Z", "hash": "a1b2c3d" },
    { "message": "fix: resolve null pointer in auth flow", "hash": "b2c3d4e" },
    { "message": "wip", "hash": "c3d4e5f" }
  ],
  "previous_version": "1.4.2",
  "repository": { "url": "https://github.com/org/repo" },
  "options": { "format": "markdown" }
}
```

### Response

| Field | Present on | Description |
|-------|------------|-------------|
| `status` | all | `success`, or `partial` when the commits carried less detail (review before publishing) |
| `signal` | all | 0 to 1: how much usable change information the commits carried. Higher is better. It is not a probability |
| `meta` | all | `commits_received`, `commits_used`, `commits_ignored`, `warnings` |
| `version` | analyze, semver | `previous` (or `null`), `suggested`, `bump` (`major`, `minor`, `patch`) and `reasoning` |
| `changelog.formatted` | analyze, changelog | The changelog in the requested format |
| `changelog.sections` | analyze, changelog | `breaking`, `added`, `changed`, `fixed`, `deprecated`, `removed`, `security`, `uncategorized` |

Entries in `breaking` to `security` have `message` (cleaned and capitalized), and `hash`, `author`, `timestamp` and `url` when known. Entries in `uncategorized` have `message`, `hash` and `reason`.

When the commits carry too little detail, the call fails with `422 INSUFFICIENT_SIGNAL`.

```json
{
  "status": "success",
  "signal": 0.92,
  "meta": { "commits_received": 3, "commits_used": 2, "commits_ignored": 1, "warnings": [] },
  "version": {
    "previous": "1.4.2",
    "suggested": "1.5.0",
    "bump": "minor",
    "reasoning": "1 new feature added, 1 bug fixed, no breaking changes detected"
  },
  "changelog": {
    "formatted": "## [1.5.0] - 2026-05-20\n### Added\n- Dark mode toggle ([a1b2c3d](https://github.com/org/repo/commit/a1b2c3d))\n...",
    "sections": {
      "breaking": [],
      "added": [{ "message": "Dark mode toggle", "hash": "a1b2c3d", "author": "paul", "timestamp": "2026-05-20T10:00:00Z", "url": "https://github.com/org/repo/commit/a1b2c3d" }],
      "changed": [],
      "fixed": [{ "message": "Resolve null pointer in auth flow", "hash": "b2c3d4e", "url": "https://github.com/org/repo/commit/b2c3d4e" }],
      "deprecated": [],
      "removed": [],
      "security": [],
      "uncategorized": [{ "message": "wip", "hash": "c3d4e5f", "reason": "insufficient signal" }]
    }
  }
}
```

### Validation codes

| Code | Field | Cause |
|------|-------|-------|
| `MISSING_COMMITS` | `commits` | Missing, not an array, or empty |
| `INVALID_COMMIT_SHAPE` | `commits.N.message` | A commit isn't an object, or its message is missing, empty or not a string |
| `COMMITS_LIMIT_EXCEEDED` | `commits` | More than 250 commits |
| `INVALID_VERSION_FORMAT` | `previous_version` | Not a semver string |
| `INVALID_PROVIDER` | `repository.provider` | Not `github`, `gitlab` or `bitbucket` |
| `INVALID_REPOSITORY_URL` | `repository.url` | Missing or malformed |

All of these are `400`. `field` names the offending field.

### GET /v1/formats

Lists the output formats. No API key needed.

```json
{ "formats": ["markdown", "json", "keepachangelog", "slack"] }
```

| Format | `changelog.formatted` |
|--------|-----------------------|
| `markdown` | Markdown with a `##` version header and `###` sections |
| `keepachangelog` | The [Keep a Changelog](https://keepachangelog.com) layout, sections in its order |
| `json` | A JSON string with `version`, `date` and `sections` |
| `slack` | Slack-formatted text with emoji section headers |

## Releases & checks

A release check reads what changed between two versions and reports evidence-backed findings. See [Release Checks & Blast Radius](/docs/release-checks).

### POST /v1/checks

Checks a release. Send either `repository` (Bumpkit fetches the diff from GitHub) or `diff`.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `base` | string | Yes | The version or ref the release starts from, such as `v1.4.2` |
| `head` | string | No | The ref being released. Defaults to the default branch. |
| `repository` | string | One of | `owner/name` or a github.com URL |
| `diff` | string | One of | A unified diff, for example from `git diff` |
| `commits` | array | No | Commit objects, as in [Changelog & semver](#changelog-semver). Fetched from GitHub when omitted. |
| `previous_version` | string | No | Version for the bump suggestion. Defaults to `base`. |
| `source` | string | No | `api` (default), `mcp`, `cli`, `github`, `gitlab`, `bitbucket`, `azure_devops` or `ghes` |
| `agent` | string | No | The coding agent that wrote the change, such as `claude-code` |
| `fast` | boolean | No | Skip the slower checks |
| `contracts` | array | No | Beta, flag `contract_checks`. `[{path, old, new}]` contract files to compare when you send a `diff`. See [Contract Checks](/docs/contract-checks). |
| `manifests` | array | No | Beta, flag `version_consistency`. `[{path, content}]` version manifests such as `package.json` or `go.mod` (64 KB each). |
| `tags` | string[] | No | Beta, flag `version_consistency`. Release tags, at most 500. |

With the regression catchers feature (Beta, flag `behavior_checks`), each commit can also carry `diff`, and findings gain `commit_sha` and `commit_subject`.


Returns `200` with a check:

| Field | Description |
|-------|-------------|
| `id` | Check ID |
| `repository`, `base`, `head`, `head_sha` | What was checked |
| `verdict` | `ship`, `review` or `block`. `block` only comes from proven high-severity findings. |
| `summary` | One-line summary |
| `version` | Suggested version, as in `/v1/semver` |
| `signal`, `changelog` | As in `/v1/analyze` |
| `stats` | `commits`, `files_changed`, `additions`, `deletions`, `truncated` |
| `ai_review` | What the AI review covered: `state` (`reviewed`, `partly_reviewed` or `could_not_review`), `reason`, `files_reviewed`, `files_not_reviewed` and `not_reviewed[]` (`path`, `reason`). See [AI review coverage](/docs/release-checks#ai-review-coverage). |
| `findings[]` | `id`, `checker`, `kind`, `severity`, `confidence`, `evidence` (`deterministic` or `inference`), `title`, `detail`, `file`, `line_start`, `line_end`, `snippet`, `fingerprint`, `suppressed` |
| `files[]` | `path`, `old_path`, `status`, `additions`, `deletions` |
| `agents` | Coding agents detected in the commits |
| `deployed_at` | When this release was deployed, if recorded |
| `created_at` | When the check ran |
| `replayed` | `true` on an [idempotent replay](/docs/api-auth#idempotency) |

Errors: `400 VALIDATION_ERROR` (missing `base`, bad `source`, neither `diff` nor `repository`), `404 REPOSITORY_NOT_FOUND` (GitHub has no such repository or refs, or it is private and the GitHub App isn't installed).

### GET /v1/checks

Lists checks, newest first.

| Query | Description |
|-------|-------------|
| `repository` | Filter by repository |
| `limit` | Default 20, maximum 100 (clamped) |
| `recorded` | Optional boolean. Exclude working-tree previews using the Releases page filter |

With `recorded=true`, checks of the same commits are grouped into one release, working-tree previews are left out, and the response also has `decisions` (check ID to gate outcome).

Returns `{"checks": [Check]}`.

### GET /v1/checks/queue

Lists GitHub pull request checks that are queued, running or retrying, plus any whose **Bumpkit release gate** check failed after retries, most recently updated first.

| Query | Description |
|-------|-------------|
| `repository` | Filter by repository |
| `limit` | Default 20, maximum 100 (clamped) |

Returns `{"queue": [{"repository", "pull_request", "head_sha", "state", "message", "updated_at"}]}`. `state` is `queued`, `running`, `retrying` or `failed`; `message` says what's happening in the words the dashboard uses. `pull_request` is left out when the event named only a commit.

### GET /v1/checks/{id}

Returns one check, with `server_fetched` when it has a gate decision: `true` when Bumpkit read the diff from your repository, `false` when your CI sent it. `404 CHECK_NOT_FOUND` if it doesn't exist.

### GET /v1/checks/{id}/radar.svg

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `blast_radar`.

The check's blast radius drawn as an SVG radar: rings are hops from the changed symbol, slices are product areas, and each affected place shows what the gate decided there. Returns `image/svg+xml`. Limited to 60 per minute per key. `404 CHECK_NOT_FOUND` for an unknown check.

### GET /v1/history

What Bumpkit remembers about a repository's releases.

| Query | Description |
|-------|-------------|
| `repository` | Required |
| `path` | Only checks that touched files under this path |
| `limit` | Default 20, maximum 100 (clamped) |

Returns `{"repository", "path", "checks": [Check]}`.

### GET /v1/history/tree

A repository's version tree: every release Bumpkit checked, newest first, with where each one is live. Query `repository` (required, `owner/name`); up to the 100 newest checks are read.

Returns `repository`, `environments` (what is live now: `[{environment, version, sha, deployed_at, current}]`, production first) and `releases`, each with `name`, `check_id`, `previous` (the release before it in the tree), `base`, `head`, `head_sha`, `commits`, `checked_at`, `verdict`, `gate` (the latest gate outcome), `deploys` (every environment it was deployed to, `current` when it is still the live one there), `incidents` (`[{id, title, started_at, resolved_at}]`) and `rollbacks` (closed-loop rollback items that undo it).

A redeploy counts: deploy `v1.0.0` again after `v1.2.0` and `v1.0.0` is current again.

### GET /v1/features

Lists the features that are on for your workspace: `{"features": ["release_checks", "release_gate", ...], "early_access": {...}}`, where `early_access` maps each Beta feature you don't have yet to a message with its request link. Clients such as the MCP server use it to offer only tools that work.

### POST /v1/findings/{id}/feedback

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `finding_feedback`.

Marks a finding real or a false positive.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `verdict` | string | Yes | `real` or `false_positive` |
| `reason` | string | No | Under 500 characters |

Returns `204`. `404 FINDING_NOT_FOUND` if the finding isn't in your workspace.

### GET /v1/scorecards

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `agent_scorecards`.

How each coding agent's releases fare. Query `days`: 1 to 365, default 30.

Returns `{"since", "days", "agents": [...]}`. Each agent has `agent`, `checks`, `ship`, `review`, `blocked`, `gated`, `needs_approval`, `gate_failed`, `proven_high`, `lines_changed`, `ship_rate`, `top_findings` (`[{kind, count}]`) and `committers` (`[{committer, releases}]`).

### GET /v1/proven

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `proven_changes`.

Which kinds of change, in which part of each repository, ship without problems over the last 90 days. Query `repository` limits it to one `owner/name`.

Returns `{"window_days", "settle_days", "generated_at", "capped", "repositories": [...]}`. Each repository has `repository`, `outcome_source_connected`, `areas`, `kinds`, `classes`, `backtest` and `notes`. Each class has `area`, `kind`, `state` (`proven`, `earning`, `paused`, `excluded` or `not_enough_signal`), `label`, `why`, `clean`, `pending`, `problems`, `need`, `problem_rate` (left out until a change has settled), `authors` (`[{name, changes}]`) and `explanation`. `backtest` has `days`, `decided`, `asked_for_review`, `would_skip`, `caused_problems`, `skipped`, `caused_by`, `summary` and `definition`. See [Proven changes](/docs/confidence-score#proven-changes).

### GET /v1/proven/class

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `proven_changes`.

One class's changes and problems. Query `repository`, `area` and `kind`, all required.

Returns `repository`, `class`, `changes` (`[{number, check_id, shipped_at, shipped_by, merge_commit, status, authors}]`, with `status` `clean`, `pending` or `problem`, and `shipped_by` `merge` or `deploy`) and `problems` (`[{number, kind, note, at, reverted_by}]`). `404 PROVEN_CLASS_NOT_FOUND` if no merged pull request falls in that class.

### GET /v1/checks/{id}/confidence

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `confidence_score`.

The release's confidence score. Returns `check_id`, `score` (0 to 100), `failure_probability`, `note`, `factors` (`[{key, label, detail, value}]`), `teams` and `scored_at`. Until Bumpkit has enough of your release outcomes, `score` and `failure_probability` are left out and `note` says what is needed. `404 CHECK_NOT_FOUND` for an unknown check. See [Confidence Score & Leadership](/docs/confidence-score).

### POST /v1/checks/{id}/outcomes

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `confidence_score`.

Records what happened after a release, so your confidence scores reflect it. Needs a key tied to a person whose role can make changes; viewers and auditors get `403 FORBIDDEN`.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `kind` | string | Yes | `revert`, `incident`, `hotfix` or `regression` |
| `note` | string | No | Up to 1000 characters |

Returns `204`. `404 NOT_FOUND` for an unknown check, `409 CONFLICT` if that outcome is already recorded. The gate records reverts on its own.

## Compatibility matrix

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `compatibility_matrix`.

See [Compatibility matrix](/docs/contract-checks#compatibility-matrix).

### GET /v1/compatibility

Returns `{"pairs": [...], "counts": {"compatible", "breaking", "not_checked"}, "method", "generated_at"}`. Each pair has `key`, `consumer`, `consumer_role`, `consumer_versions`, `provider`, `provider_role`, `provider_repository`, `provider_versions`, `verdict`, `reason`, `breaks`, `versions`, `declared_in`, `source`, `synced_at` and `explanation`. Query `repository`: only pairs this repository declares or provides.

### GET /v1/compatibility/pairs/{key}

Returns one pair. `404 COMPATIBILITY_PAIR_NOT_FOUND` when no declared pair has that key.

### GET /v1/compatibility/export

Downloads the matrix. Query `format`: `csv` or `json`, required. Query `repository`: as above. `400 VALIDATION_ERROR` for another format.

### POST /v1/compatibility/sync

Owner or admin. Body `{"repository": "owner/name", "policy": "..."}`. Reads the `compatibility` section into the matrix. With the GitHub App on the repository, Bumpkit reads `bumpkit.yml` from the default branch and ignores `policy`. Otherwise it uses `policy`, and the pair's `source` is `client`. Returns `{"repository", "source", "components", "pairs", "removed"}`. A file with no section removes the repository's pairs. `400 COMPATIBILITY_POLICY_NOT_FOUND` when there's no file to read, and `400 INVALID_POLICY` when the section is invalid. Writes an audit record.

## Cross-repo impact

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flags: `cross_repo_impact`, `dependency_autopilot`.

See [Cross-Repo Impact](/docs/cross-repo). These endpoints need a key tied to a person. With `cross_repo_impact` on, the gate can also return the rules `cross_repo.block_on_consumer_break` and `cross_repo.require_approval`.

### GET /v1/service-graph

Flag `cross_repo_impact`. Returns `{"repositories": [...]}`. Each repository has `repository`, `default_branch`, `head_sha`, `truncated`, `refreshed_at`, `provides` and `consumes` (`[{kind, name, version, path, line, direct}]`), and `consumers`.

### POST /v1/service-graph/refresh

Flag `cross_repo_impact`. Owner only. Rebuilds the graph from the workspace's repositories. Returns `{"repositories": n}`, with `error` when some repositories failed. `502 UPSTREAM_ERROR` when nothing could be read.

### GET /v1/autopilot/proposals

Flag `dependency_autopilot`. Lists dependency upgrade proposals, pending and failed first. Query `limit`: 1 to 200, default 50.

Each proposal has `id`, `repository`, `ecosystem`, `name`, `from`, `to`, `priority`, `breaking`, `reasons`, `advisories`, `edits` (`[{path, line, old, new, why}]`), `todos` (`[{path, line, code, why}]`), `status`, `pull_request_url`, `error`, `decided_by`, `decided_at` and `created_at`.

### POST /v1/autopilot/scan

Flag `dependency_autopilot`. Owner only. Scans dependencies and creates proposals. Returns `{"checked": n, "proposed": n}`.

### POST /v1/autopilot/proposals/{id}/approve

Flag `dependency_autopilot`. Owner only. Opens the upgrade pull request; Bumpkit never merges it. Returns `200` with the proposal, `status` `opened`. `404 NOT_FOUND`, `409 CONFLICT` if already decided, `502 UPSTREAM_ERROR` if the pull request couldn't be opened.

### POST /v1/autopilot/proposals/{id}/dismiss

Flag `dependency_autopilot`. Owner only. Returns `204`. Same errors as approve.
