# API: Authentication & errors

What every request shares.

## Authentication

Send your API key in the `x-api-key` header on every request:

```bash
curl https://bumpkit.dev/v1/me -H "x-api-key: bk_live_..."
```

`Authorization: Bearer` is not accepted on `/v1` endpoints. The SCIM API is the one exception: it takes a separate SCIM token as a bearer token (see [SSO & SCIM](/docs/api-admin#sso-scim)).

| Item | Detail |
|------|--------|
| Key prefix | `bk_live_`. A key without it fails with `INVALID_API_KEY`. |
| Display prefix | The first 16 characters, shown as `prefix` in key listings. |
| Storage | Bumpkit stores a hash. The full key is shown once, when you create it. |
| Expiry | Optional `expires_at`. An expired key fails with `REVOKED_API_KEY`. |
| Getting a key | Create one in the [dashboard](https://dashboard.bumpkit.dev), run `bumpkit login`, or call [`POST /v1/keys`](/docs/api-admin#post-v1-keys). |

Every key belongs to one workspace. There are no per-key scopes. What a key can do depends on two things:

- **Workspace endpoints** (keys, team, settings, integrations, billing, audit, override reviews, rules and similar) need a key created by a person. They act as that person, with that person's role. A key that isn't tied to a person gets `403 FORBIDDEN`.
- **Person-only actions** (gate overrides, agent command decisions, and approving and cutting a release batch) need a person signed in to the dashboard. Any key gets `403 HUMAN_APPROVAL_REQUIRED`, whatever its creator's role.
- **Owner-only actions** need the key's creator to be a workspace owner or admin. With [roles](/docs/api-admin#roles) on, some actions also accept another role that holds the permission they name.

Endpoints without either requirement work with any key in the workspace.

## Errors

Every error uses the same shape:

```json
{
  "status": "error",
  "error": {
    "code": "MISSING_COMMITS",
    "message": "commits array must contain at least one entry",
    "field": "commits",
    "suggestion": "Provide an array of commit objects with at least one entry.",
    "explanation": "The request has no commits, or commits isn't an array with at least one commit.",
    "next_step": "Send commits as an array with at least one commit object.",
    "docs_url": "https://bumpkit.dev/docs/explanations#missing-commits"
  }
}
```

`field` appears only on errors from the changelog endpoints: the validation codes below and `422 INSUFFICIENT_SIGNAL`. `suggestion` appears only for codes that have one. Errors also carry `explanation`, `next_step` and `docs_url`, which link to its entry in [Why am I seeing this?](/docs/explanations#api-errors). Successful responses have no envelope: they are the resource itself.

### Common codes

| HTTP | Code | Meaning |
|------|------|---------|
| 400 | `VALIDATION_ERROR` | The body doesn't match the endpoint's shape, or a field is invalid. The message says which. |
| 400 | `INVALID_JSON` | The body isn't valid JSON, or isn't a JSON object or array. |
| 400 | `INVALID_POLICY` | The `bumpkit.yml` sent to the gate doesn't parse. The gate fails closed. |
| 401 | `MISSING_API_KEY` | No `x-api-key` header. |
| 401 | `INVALID_API_KEY` | The key is malformed or doesn't exist. |
| 401 | `REVOKED_API_KEY` | The key was revoked or has expired. |
| 401 | `INVALID_SIGNATURE` | An inbound webhook's signature doesn't match. |
| 403 | `FORBIDDEN` | Your role can't do this, or the key isn't tied to a person. |
| 403 | `ROLE_REQUIRED` | Evidence exports and the audit stream need a key created by an owner, admin or auditor. Publishing release docs needs a key created by an owner, admin, policy admin, approver or member. |
| 403 | `FEATURE_EARLY_ACCESS` | The endpoint belongs to a Beta feature your workspace doesn't have access to yet. The message links to the page where you request it. |
| 404 | `NOT_FOUND` | Unknown route, a feature that isn't available, or a resource that doesn't exist. For an unknown route the message is `Route not found.` |
| 404 | `CHECK_NOT_FOUND`, `FINDING_NOT_FOUND`, `LESSON_NOT_FOUND`, `MEMORY_NOT_FOUND`, `DEPLOYMENT_NOT_FOUND`, `REPOSITORY_NOT_FOUND` | The resource doesn't exist in your workspace. |
| 409 | `CONFLICT` | The change conflicts with current state, for example a duplicate. |
| 413 | `PAYLOAD_TOO_LARGE` | The body is over the endpoint's [size limit](#body-size-limits). |
| 422 | `INSUFFICIENT_SIGNAL` | The changelog endpoints found no usable commits. |
| 429 | `RATE_LIMIT_EXCEEDED` | A per-minute or per-endpoint limit was hit. Every `429` carries `Retry-After` in seconds. |
| 500 | `INTERNAL_ERROR` | Server error. |
| 502 | `UPSTREAM_ERROR` | A provider such as GitHub, Stripe or an integration failed. |
| 503 | `SERVICE_UNAVAILABLE` | Temporary outage. |
| 503 | `POLICY_UNAVAILABLE` | The gate reads `bumpkit.yml` through the GitHub App or a GitHub Enterprise Server connection, and the host didn't return it or couldn't say whether the App is installed. The gate fails closed. |
| 503 | `LLM_NOT_CONFIGURED` | This feature isn't available for your workspace right now. |

Every code, with what to do about it, is listed in [Why am I seeing this?](/docs/explanations#api-errors).

### Body size limits

| Endpoint | Limit |
|----------|-------|
| `POST /v1/checks`, `POST /v1/deployments` | 10 MB |
| `POST /v1/rollbacks/{id}/open` | 8 MB |
| Everything else | 1 MB |

A body over the limit fails with `413 PAYLOAD_TOO_LARGE`, and a body that isn't valid JSON with `400 INVALID_JSON`, before authentication runs.

## Rate limits

Per-minute limits are separate from billing. The workspace limit is 120 requests per minute, shared by all its keys, and some endpoints have their own limits.

Authenticated responses carry these headers, so you can budget calls before you hit a limit:

```text
X-RateLimit-Limit-Minute: 120
X-RateLimit-Remaining-Minute: 43
X-RateLimit-Reset-Minute: 1716249660
```

Reset values are Unix epoch seconds. When the limit is hit the response is `429` with `RATE_LIMIT_EXCEEDED`, the same headers and `Retry-After` in seconds.

## Credits

1 credit = 1 release check = $0.10. Re-runs of the same commit are free. Every workspace gets 60 free credits each calendar month (UTC), no card needed. No seats, no repository limits. Enterprise is a custom annual contract.

Credit purchases aren't enabled yet. To buy more credits, contact the Bumpkit team at hello@bumpkit.dev. [GET /v1/billing/usage](/docs/api-admin#get-v1-billing-usage) shows what each period used.

Every `429` carries `Retry-After` in seconds. Wait that long, then retry. Endpoints with their own limits say so below.

## Idempotency

`POST /v1/checks` and `POST /v1/gate` are replay-safe. Send an `Idempotency-Key` header and retry with the same key: you get the stored result instead of a second run.

```bash
curl https://bumpkit.dev/v1/gate \
  -H "x-api-key: $BUMPKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: deploy-7f3a9c" \
  -d '{"repository": "acme/api", "base": "v1.4.2", "head": "main"}'
```

| Rule | Behavior |
|------|-----------|
| Key format | 1 to 200 characters: letters, digits, `.`, `:`, `-`, `_`. Anything else is `400 INVALID_IDEMPOTENCY_KEY`. |
| Pinned requests | A request with a `diff`, or with `base` and `head` both full 40-character SHAs, is replayed automatically, with or without a header, until something that affects the result changes, such as approvals, CI results, org policy or a freeze. Then the call runs again. |
| Other requests | Replayed only when you send `Idempotency-Key`. The key covers the request body. For the gate, `approvals` are not part of it. |
| Replay | `200` with the stored body and `"replayed": true`. No new check or decision is stored, and the call doesn't use a credit. |
| Same key, different body | `422 IDEMPOTENCY_KEY_REUSED`. |
| Same request still running | The server waits up to 2 minutes for the first call, then returns `409 REQUEST_IN_PROGRESS`. Retry. |
| Failed runs | Not stored. A retry runs again. |
| Retention | 1 day. |
| Early expiry | A stored result runs again after new finding feedback, an override, or a change freeze that starts, ends or changes. |
| Scope | Per workspace and per endpoint. |
| Data retention | Results are stored for replay only with the `standard` [data retention](/docs/api-admin#data-handling) mode. With `findings_only` or `ephemeral`, every call runs again. |

Other endpoints have no idempotency header. `POST /v1/claims` is idempotent for the same agent and session; the endpoint descriptions note others.

## Pagination

There are no cursors. List endpoints take a `limit` query parameter and return the newest items first. Each endpoint gives its default and maximum. An out-of-range `limit` falls back to the default, except where the endpoint says it is clamped.

SCIM list endpoints use `startIndex` and `count`.

## Versioning

The API is versioned in the path. Everything here is `/v1`. Fields can be added to responses without notice, so ignore fields you don't recognize. Fields marked optional are left out when empty.
