# API: Admin

Run your workspace over the API.

## Compliance

See [Compliance Evidence](/docs/compliance). Export periods use either `from` and `to` (`YYYY-MM-DD`, inclusive) or `days` (1 to 730, default 90).

### GET /v1/evidence

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

Change management evidence, tamper-evident. Query `from`, `to`, `days` and `format=csv`.

The evidence endpoints need a key created by an owner, admin or auditor (the `evidence.export` permission). Other roles, and keys not tied to a person such as CI keys, get `403 ROLE_REQUIRED`, and each refusal is written to the audit log as `evidence.export.denied`.

Returns a JSON (or CSV) attachment with `manifest` (`workspace`, `from`, `to`, `generated_at`, `records`, `gated`, `emergency_changes` and `overdue_reviews`), `records` and `emergency_changes`. Records carry `policy_source` when the gate decision recorded one. With Close the loop on (Beta, flag `close_the_loop`), the export also has `closed_loop` (each item's `id`, `kind`, `repository`, `title`, `status`, `routed_to`, `fix_url`, `verified_by`, `verified_sha`, `verified_at`, `created_at` and `steps`), and the manifest gains `closed_loop`.

### GET /v1/evidence/signed

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

A zip with `evidence.json`, `evidence.csv`, `manifest.json` and `manifest.sig` (an Ed25519 signature of `manifest.json`), plus `governance.json` and `governance.csv` with `governance_evidence` on. The manifest lists each file's SHA-256 and the signing key; fetch the public key from `key_url`. `503 SIGNING_UNAVAILABLE` when evidence signing isn't available; contact Bumpkit support.

### GET /v1/evidence/governance

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

Agent governance evidence: every agent-written change and how it was reviewed. Query as above, plus `format=csv`.

Returns `workspace`, `from`, `to`, `generated_at`, `rule`, `summary` and `changes`. Each change has `check_id`, `checked_at`, `repository`, `pull_request`, `head_sha`, `agents`, `decision_id`, `outcome`, `approval_required`, `approvers`, `approvals_source`, override fields, `status`, `gaps` and `evidence_hash`.

### GET /v1/audit

Workspace audit log, newest first. Owners, admins and auditors only (the `audit.read` permission). Query `limit`: 1 to 500, default 100. Returns `{"events": [{id, actor, actor_email, via, action, target, created_at}]}`.

### GET /v1/audit/events

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

The audit stream as a signed export, for a SIEM. Query as above, plus `format=csv`. Needs a key created by an owner, admin or auditor (`audit.read`). Other roles and keys not tied to a person get `403 ROLE_REQUIRED`, audited as `audit.read.denied`. Up to 50,000 events, or `400 EXPORT_TOO_LARGE`.

Returns JSON Lines (`application/x-ndjson`) or CSV. Each event has `id`, `sequence`, `workspace_id`, `type`, `source_id`, `occurred_at`, `actor` (`email`, `via`), `action`, `target` and `data`. The response headers sign the body:

| Header | Value |
|--------|-------|
| `X-Bumpkit-Signature` | Base64 Ed25519 signature of the body |
| `X-Bumpkit-Key-Id` | The signing key |
| `X-Bumpkit-Content-Sha256` | Hex SHA-256 of the body |
| `X-Bumpkit-Events` | Number of events |

### Audit streaming and auditor links

> **Beta, early access.** Flags: `siem_streaming` for destinations, `auditor_access` for links. Destinations need `integrations.manage`; links need `workspace.manage`. Responses carry `Cache-Control: no-store`.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/audit/streaming/sinks` | `{"sinks": [...]}`: `id`, `kind`, `name`, `endpoint`, `enabled`, `pending`, `dead_letters`, `last_delivered_at` and `has_delivery_error` |
| POST | `/v1/audit/streaming/sinks` | Send `kind`, `name`, `url`, `secret` and `config`. Returns `201 {"id"}` |
| POST | `/v1/audit/streaming/sinks/{id}/test` | Sends a test event. Returns `204`, or `502 DELIVERY_FAILED` when the destination refuses it |
| POST | `/v1/audit/streaming/sinks/{id}/enable` | Resumes delivery. Returns `204` |
| POST | `/v1/audit/streaming/sinks/{id}/disable` | Pauses delivery. Returns `204` |
| DELETE | `/v1/audit/streaming/sinks/{id}` | Removes the destination. Returns `204` |
| GET | `/v1/audit/streaming/dead-letters` | `{"dead_letters": [{id, sink_id, events, created_at}]}` |
| POST | `/v1/audit/streaming/dead-letters/{id}/redeliver` | Sends a failed batch again. Returns `204`, or `502 DELIVERY_FAILED` |
| GET | `/v1/auditor-links` | `{"links": [...]}`: `id`, `label`, `created_by`, `expires_at`, `revoked_at`, `last_used_at` and `created_at` |
| POST | `/v1/auditor-links` | Send `label` and `days`. Returns the link with its `url`, once |
| DELETE | `/v1/auditor-links/{id}` | Revokes the link. Returns `204` |

See [Compliance](/docs/compliance#audit-streaming) for destination kinds and settings.

### GET /.well-known/bumpkit-evidence-keys/{id}

> **Beta, early access.** Flag: `signed_evidence`.

Public evidence signing key. No API key needed; limited to 60 requests per minute per IP. Returns `key_id`, `algorithm`, `public_key`, `scope`, `created_at` and `retired_at`. `404 KEY_NOT_FOUND` for an unknown key.

## Confidence & leadership

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

See [Confidence Score & Leadership](/docs/confidence-score). The per-release score is [`GET /v1/checks/{id}/confidence`](/docs/api-checks#get-v1-checks-id-confidence).

### GET /v1/risk

Flag `confidence_score`. Where releases fail most.

| Query | Description |
|-------|-------------|
| `repository` | Filter |
| `scope` | `repository`, `area` or `file` (default) |
| `limit` | 1 to 500, default 50 |

Returns `{"risk": [{repository, scope, path, changes, failures, risk}]}`.

### GET /v1/reports/leadership

Flag `leadership_dashboards`. Release outcomes for leadership.

| Query | Description |
|-------|-------------|
| `from`, `to` | `YYYY-MM-DD`, together. Otherwise `days`. |
| `days` | 1 to 730, default 90 |
| `repository`, `team` | Filters |
| `format` | `csv` for a CSV attachment |

Returns `from`, `to`, `repository`, `team`, `totals` (`releases`, `shipped`, `blocked`, `needs_approval`, `overridden`, `failed`, `incidents`, `avg_confidence`, `avg_risk`), `authorship` (agent and human releases, lines and incidents, shares, and `matrix`: `writers`, `committers` (`[{committer, person, releases, lines, cells}]`, each cell `{writer, releases, lines, shipped, blocked, ship_rate}`), `totals`, `people_lines`, `people_agent_lines`, `people_agent_share` and `outcomes`: per writer, `releases`, `passed`, `needs_approval`, `blocked`, `pass_rate`, `approval_rate` and `blocked_rate`; and `coverage`, as in `GET /v1/reports/authorship-coverage`), `trend` (`[{week, releases, avg_confidence, avg_risk}]`), `repositories` and `teams`.

### GET /v1/reports/authorship-coverage

Flag `leadership_dashboards`. How many of your releases have verified authorship. See [Authorship coverage](/docs/confidence-score#authorship-coverage).

| Query | Description |
|-------|-------------|
| `from`, `to` | `YYYY-MM-DD`, together. Otherwise `days`. |
| `days` | 1 to 730, default 90 |
| `repository` | One repository; omit for every repository |

Returns `from`, `to`, `repository` and `coverage` (`releases`, `verified`, `inferred_only`, `no_signals`, `instrumented`, `headline` and `trend`: `[{week, releases, verified, inferred_only, no_signals}]`).

### GET /v1/reports/readiness

Flag `leadership_dashboards`. The release readiness report. See [Release readiness report](/docs/confidence-score#release-readiness-report).

| Query | Description |
|-------|-------------|
| `from`, `to` | `YYYY-MM-DD`, together. Otherwise `days`. |
| `days` | 1 to 730, default 90 |
| `repository` | One repository; omit for every repository |
| `format` | `json` (default), `csv` or `pdf`. CSV and PDF come back as attachments. Anything else is `400 VALIDATION_ERROR`. |

Returns `title`, `repository`, `from`, `to`, `generated_at`, `verdict` (`status`: `ready`, `conditions`, `hold` or `no_releases`; `label` and `text`), `messages` (`[{text}]`), `releases` (counts and `rows`), `reasons` (`rows` of `{rule, outcome, status, releases, example}`), `authorship` (`writers` and `committers`, each `{name, role, releases, commits}`, and `coverage`: `{title, releases, verified, inferred_only, no_signals, instrumented}`), `findings` (`high`, `medium`, `low`, `info` and `top`), `approvals` (`requested`, `approved`, `waiting`, `closed`, `median_minutes`, `p90_minutes`, `longest_minutes` and `slowest`). Each section has a `title`.

### GET /v1/confidence/overview

Flag: `confidence_score`. How accurate scores have been, and the riskiest files and areas. Needs a key tied to a person. Query `repository` and `days`. Returns `workspace_id`, `repository`, `model`, `files`, `areas`, `recent`, `file_limit`, `area_limit`, `recent_limit`, `recent_scope` and, with the `feedback` flag, `feedback`. Use `bumpkit report confidence status` or the `report_confidence_status` MCP tool.

## Workspace & team

### GET /v1/overview

This workspace's release activity for the current period. Needs a key tied to a person. Returns `workspace_id`, `period_start`, `period_end`, `as_of`, `activity` (`checks`, `runs`, `passed`, `needs_approval`, `blocked`, `not_checked`, `repositories`), `recent`, `decisions`, `recent_limit`, `repositories`, `private_repositories`, `people` and, with the `explanations` flag, `onboarding`. Use `bumpkit status` or the `status` MCP tool.

### GET /v1/account/github

Whether your Bumpkit login is linked to a GitHub account. Needs a key tied to a person. Returns `linked`, `login`, `message` and `link_url`. Use `bumpkit account link-github` or the `github_link_status` MCP tool.

### POST /v1/features/{name}/request

Asks for early access to a Beta feature. Read-only roles get `403 FORBIDDEN`. Returns `{"key", "status", "created"}`: `status` is `requested`, or `enabled` (with no `created`) when your workspace already has it. An unknown feature, or one that isn't in early access, answers `404 NOT_FOUND`, and too many requests answer `429 RATE_LIMIT_EXCEEDED`. Use `bumpkit features request FEATURE` or the `features_request` MCP tool.

### GET /v1/onboarding

The first-run checklist. Needs a key tied to a person. Returns `workspace_id`, `workspace_name`, `workspace_confirmed`, `github_app_installed`, `release_decision_received`, `dismissed`, `can_confirm` and `can_dismiss`. Use `bumpkit onboarding view` or the `onboarding_view` MCP tool.

### POST /v1/onboarding/workspace

Confirms the workspace name. Owners and admins only. Send `workspace_id`, which must match the key's workspace, and `name` (up to 64 characters). Returns the checklist. Use `bumpkit onboarding confirm` or the `onboarding_confirm` MCP tool.

### POST /v1/onboarding/dismiss

Flag: `explanations`. Dismisses the checklist. Owners and admins only. Send `workspace_id`, which must match the key's workspace. Returns `204`. Use `bumpkit onboarding dismiss` or the `onboarding_dismiss` MCP tool.

### GET /v1/me

The key's identity: `workspace_id`, `workspace_name`, `plan`, `key_label`, `key_prefix` and `user_email`.

### POST /v1/me/revoke

Revokes the key making the request. Returns `{"revoked": true}`. `bumpkit logout` uses it.

### GET /v1/workspaces

Needs a key tied to a person. Returns `{"workspaces": [{workspace_id, name, role, current}]}`: every workspace that person belongs to. `current` is `true` for the workspace the key is bound to.

### POST /v1/workspaces

Creates a workspace owned by the key's person. Any role in the key's workspace can do it. The new workspace gets its own team, repositories, audit log and monthly free credits.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Workspace name, up to 64 characters |

Returns `201 {"workspace_id", "name", "role": "owner", "current": false}`. The key stays bound to its own workspace, so later requests still act there. To use the new workspace from the CLI, run `bumpkit login` and choose it. `400 VALIDATION_ERROR` if the name is empty, longer than 64 characters or has unprintable characters. Each person can create up to 10 workspaces in 24 hours; past that you get `429 RATE_LIMIT_EXCEEDED` with a `Retry-After` header. The new workspace's audit log records it as `workspace.create`.

### DELETE /v1/workspace

Owners only, signed in to the dashboard: an API key gets `403 HUMAN_APPROVAL_REQUIRED` with the link. Send `workspace_id`, the exact `name` and `confirmed: true`. Deletes the workspace and everything in it. Returns `204`, or `400` when the name doesn't match.

### GET /v1/team

Needs a key tied to a person. Returns `members` (`[{id, email, role, created_at}]`) and `invitations` (`[{id, email, expires_at, created_at}]`). With roles on, members also have `active` and invitations `role`.

### POST /v1/team/invitations

Emails someone an invitation. Owner only (with roles: `team.manage`). Workspaces can invite unlimited teammates. Existing Bumpkit accounts can join additional workspaces without creating another account. An API key can invite as `member` or `viewer`; any higher role needs a person in the dashboard, and so does an invitation when Bumpkit can't send email.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | Yes | Who to invite |
| `role` | string | No | Beta, flag `roles`. Default `member`. |

Returns `201 {"email", "role", "emailed"}`. An API key never gets an invitation link: `403 HUMAN_APPROVAL_REQUIRED` for what needs a person, `502` when the email couldn't be sent (cancel it and invite again). `409 CONFLICT` if they're already a member or invited.

### DELETE /v1/team/invitations/{id}

Owner only (with roles: `team.manage`). Returns `204`, or `404 NOT_FOUND`.

### DELETE /v1/team/members/{id}

Owner only (with roles: `team.manage`). You can't remove yourself. The member stays out of first sign-in and IdP-managed access until you invite them again or provision them through SCIM. Returns `204`, or `404 NOT_FOUND`.

### PATCH /v1/team/members/{id}

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

Changes a member's role. Needs `roles.manage` (owners and admins). Only an owner can make someone an owner or change an owner's role, and you can't change your own role.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `role` | string | Yes | `owner`, `org_admin`, `policy_admin`, `approver`, `member`, `viewer` or `auditor` |

Returns `204`. Moving someone to `viewer` or `auditor` revokes their API keys.

### GET /v1/settings

Needs a key tied to a person. Returns `name`, `plan`, `changelog` (`public`, `slug`, `title`, `repository`, `url`) and `memory_approval`. With grouped releases on, it also returns `draft_after_merge` and `publish_audiences`, the audience whose notes each publish target gets. With CVE exposure on, it also returns `vulnerability_alerts` and `auto_fix_prs`.

### PATCH /v1/settings

Owner only (with roles: `workspace.manage`). Send any of:

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Workspace name, up to 64 characters |
| `changelog.public` | boolean | Publish the [changelog page](/docs/changelog-pages) |
| `changelog.slug` | string | 3 to 40 lowercase letters, numbers and hyphens. Required to publish. |
| `changelog.title` | string | Page title |
| `changelog.repository` | string | Repository the page covers |
| `memory_approval` | boolean | Beta, flags `codebase_memory` and `memory_approval`. Require approval for agent memories. |
| `draft_after_merge` | boolean | Beta, flag `grouped_releases`. Draft a grouped release after every merge to the default branch. On by default. `400` while grouped releases are off. |
| `publish_audiences` | object | Beta, flag `grouped_releases`. Target to audience, e.g. `{"slack": "support"}`. Targets: `changelog`, `github_release`, `slack`, `discord`, `notion`, `confluence`. Defaults: `customer` for `changelog` and `github_release`, `engineering` for the rest. An empty audience resets a target to its default. Audited as `settings.publish_audiences`. |
| `vulnerability_alerts` | string | Beta, flag `code_index`. Which new advisories alert: `all` (default), `reachable`, `critical_high` or `off`. See [Vulnerability alerts](/docs/code-index#vulnerability-alerts). |
| `auto_fix_prs` | string | Beta, flag `code_index`. Open fix pull requests automatically for `off` (default), `critical` or `critical_high`. |

Returns the settings. `409 CONFLICT` if the slug is taken.

## Keys & billing

### GET /v1/keys

Needs a key tied to a person. Returns `{"keys": [{id, prefix, label, revoked, created_at, last_used_at, expires_at}]}`.

### POST /v1/keys

Creates a key. Needs a key tied to a person (with roles: `keys.manage`, which viewers and auditors don't have).

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `label` | string | Yes | Up to 64 characters |
| `expires_at` | string | No | RFC 3339, in the future |

Returns `201` with the key's fields plus `key`, the full key. It is shown only here.

### DELETE /v1/keys/{id}

Deletes a key. You can delete a key you created; owners and admins can delete any key in the workspace. Returns `204`, `403 FORBIDDEN` for anyone else, with the refusal in the audit log as `key.delete.denied`, or `404 NOT_FOUND`.

### GET /v1/billing

Returns workspace usage: `private_repos_gated`, `people`, the operational `per_minute_limit`, `period_ends` and `repositories`. Repositories and teammates are unlimited. For credits, use [GET /v1/billing/usage](#get-v1-billing-usage).

### GET /v1/billing/usage

Owners and admins. Credits for one billing period. Query `period`: `YYYY-MM`, UTC, not in the future; default the current month. Returns `period`, `period_start`, `period_end`, `current`, `pricing` (`unit`, `price_per_check_cents`, `free_checks_per_month`, `estimates`, `purchasing_enabled`), `summary` (`credits_used`, `free_credits_used`, `free_credits_remaining`, `paid_credits`, `free_reruns` and, with estimates on, `estimated_charge_cents`), `by_repository`, `by_source`, `by_outcome` and `by_actor` (each `[{key, label, kind, credits, share, paid_credits, free_reruns, estimated_charge_cents}]`) and `daily` (`[{date, credits, free_reruns}]`).

Free credits are used in time order: the period's first 60 checks are free and the rest are paid. Free re-runs are replayed decisions and never use a credit. In `by_outcome`, `not_checked` is labeled "Checked, no gate decision": A release check that ran without a release gate decision, for example from the CLI or API. It uses a credit like any other check. In `by_actor`, `label` names the real source: `CLI · <host>` for a CLI login, `API key · <name>` (or `API key · <prefix>…` when the key has a placeholder name), `MCP · <client or agent>`, `GitHub App · automatic on pull requests`, an agent's name or a person. Others get `403 FORBIDDEN`. The dashboard's Usage and billing page, `bumpkit billing usage` and the `get_billing_usage` MCP tool show these exact numbers.

### GET /v1/billing/usage/checks

Owners and admins. The period's checks and free re-runs, newest first, 25 a page. Query `period` and `page`. Returns `period`, `page`, `per_page`, `total` and `events` (`[{id, at, check_id, repository, pull_request, head_sha, head_ref, source, source_label, outcome, outcome_label, actor, actor_kind, charge, estimated_charge_cents}]`). `charge` is `free`, `paid` or `free_rerun`.

### POST /v1/billing/usage/export

Owners and admins. The period's billable checks as CSV, oldest first: `time_utc`, `check_id`, `repository`, `pull_request`, `head_sha`, `source`, `outcome`, `triggered_by`, `triggered_by_kind`, `credits`, `charge` and, with estimates on, `estimated_charge_usd`. Query `period`. Each export is written to the audit log as `billing.export`. More than 100,000 checks returns `400 EXPORT_TOO_LARGE`.

### POST /v1/billing/portal

Owner only. Returns `{"url"}`, the Stripe billing portal.

## MCP connections

Apps connected through the hosted MCP connector. See [MCP Server](/docs/mcp).

### GET /v1/mcp/connect

How to connect Claude, Codex or Grok. Works with any key. Returns `url`, `apps`, and the setup steps and commands for each app. Use `bumpkit mcp connect` or the `mcp_connect` MCP tool.

### GET /v1/mcp/connections

Needs a key tied to a person. Returns `{"connections": [{id, client, workspace_id, workspace, user_id, user, created_at, last_used_at}]}`. Use `bumpkit mcp connections list` or the `mcp_connections_list` MCP tool.

### DELETE /v1/mcp/connections/{id}

Revokes a connection, so the app must sign in again. Owners and admins can revoke anyone's; other members revoke their own, and read-only roles get `403 ROLE_REQUIRED`. Returns `{"revoked": true}`, or `404 NOT_FOUND`. Use `bumpkit mcp connections revoke` or the `mcp_connections_revoke` MCP tool.

## SSO & SCIM

> **Beta, early access.** Flags: `sso`, `scim`, `roles`. Without `roles`, `PATCH /v1/team/members/{id}` answers `403 FEATURE_EARLY_ACCESS` with a link to request access; SCIM answers `401`.

See [SSO, SCIM & Roles](/docs/sso). SSO sign-in runs on the dashboard. Configure it there or with the identity endpoints below.

### Roles

With the `roles` flag on, each workspace action needs a permission:

| Role | Permissions |
|------|-------------|
| `owner` | Everything |
| `org_admin` | Everything except deleting the workspace and managing billing |
| `policy_admin` | `gate.override`, `gate.review`, `policy.manage`, `keys.manage`, `incident.resolve`, `write` |
| `approver` | `gate.review`, `keys.manage`, `incident.resolve`, `write` |
| `member` | `keys.manage`, `incident.resolve`, `write` |
| `viewer` | Read only |
| `auditor` | `audit.read`, `evidence.export` |

Endpoints that say "owner only" accept owners and admins, or the specific permission named. Endpoints that say "needs an owner" accept owners only. Change roles with [`PATCH /v1/team/members/{id}`](#patch-v1-team-members-id).

### SCIM 2.0

Base URL: `https://bumpkit.dev/scim/v2`

Authenticate with a SCIM token as a bearer token:

```bash
curl https://bumpkit.dev/scim/v2/Users -H "Authorization: Bearer bk_scim_..."
```

Owners and admins create SCIM tokens in the dashboard or with `POST /v1/identity/scim/tokens`. A token is shown once and stored as a hash. A missing or wrong token, or a workspace with `scim` off, gets `401`.

Responses are `application/scim+json`. Errors use the SCIM error shape: `{"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": "400", "detail": "...", "scimType": "invalidValue"}`.

| Method | Path | Notes |
|--------|------|-------|
| GET | `/scim/v2/ServiceProviderConfig` | PATCH and filter supported; bulk, sort, etag and change password not |
| GET | `/scim/v2/ResourceTypes` | `User`, `Group` |
| GET | `/scim/v2/Schemas` | User and Group schemas |
| GET | `/scim/v2/Users` | Filter `userName eq "x"`, `emails.value eq "x"` or `externalId eq "x"` |
| POST | `/scim/v2/Users` | `201` with `Location` |
| GET | `/scim/v2/Users/{id}` | |
| PUT | `/scim/v2/Users/{id}` | Replaces the user; the role is kept |
| PATCH | `/scim/v2/Users/{id}` | `add`, `replace`, `remove` on `active`, `userName`, `externalId`, `displayName` |
| DELETE | `/scim/v2/Users/{id}` | Deactivates the user and revokes their API keys. `204`. |
| GET | `/scim/v2/Groups` | Filter `displayName eq "x"` |
| POST | `/scim/v2/Groups` | `displayName` (required), `members`. `201` with `Location`. |
| GET | `/scim/v2/Groups/{id}` | |
| PUT | `/scim/v2/Groups/{id}` | Replaces name, `externalId` and members |
| PATCH | `/scim/v2/Groups/{id}` | `members` add, replace, remove (with `members[value eq "id"]`), `displayName` |
| DELETE | `/scim/v2/Groups/{id}` | `204`. Members' roles are re-synced. |

Lists take `startIndex` (from 1) and `count` (default 100, maximum 200) and return a SCIM `ListResponse`.

User rules:

- `userName` is the email. Its domain must be verified for the workspace.
- A new user gets the SSO connection's default role, or `member`. There's no member limit.
- Owners can't be deactivated over SCIM.
- Map groups to roles in the dashboard. When a mapped group's members change, their roles are recomputed; the highest mapped role wins. Owners are never changed.

Rate limits: 10 requests per second per token (burst 100, `Retry-After: 1`), and 20 failed requests per minute per IP (`Retry-After: 60`).

## Data handling

> **Beta, early access.** Flags: `zero_retention`, `regional_hosting`. Without `zero_retention`, these endpoints answer `403 FEATURE_EARLY_ACCESS` with a link to request access.

See [Data Handling](/docs/data-handling). These endpoints need a key tied to a person. Changes are owner only.

### GET /v1/data

How the workspace's data is handled. Returns `data_retention` (`standard`, `findings_only` or `ephemeral`), `label`, `description`, `request_log_days`, `findings_days` (`0` means forever), `region`, `server_region`, `regions`, `region_bound`, `not_region_bound` and `stored` (`[{name, count, contains, code}]`, what the workspace keeps).

### PATCH /v1/data

Owner only. Send at least one of:

| Field | Type | Description |
|-------|------|-------------|
| `data_retention` | string | `standard`, `findings_only` or `ephemeral`. Leaving `standard` scrubs stored code right away. |
| `request_log_days` | integer | 1 to 3650, or 0 to keep forever |
| `findings_days` | integer | 1 to 3650, or 0 to keep forever |
| `region` | string | Flag `regional_hosting`. Pin the workspace to the region its data is stored in (`server_region`), or `""` to unpin. Without the flag, `403 FEATURE_DISABLED`. |

Fields are applied in the order region, retention, windows, and each is saved as it goes, so an error on a later field leaves earlier ones changed. Returns the same body as `GET /v1/data`.

When a workspace is pinned to another region, every API request sent to another region's API fails with `421 WRONG_REGION`. The response has `X-Bumpkit-Region` and, when known, a `Location` header with the right API URL.

### GET /v1/data/export

Owner only. Limited to 5 per hour per workspace (`Retry-After: 3600`). Returns a JSON attachment `bumpkit-workspace-export.json` with `format`, `exported_at`, `exported_by` and one section for each kind of data the workspace stores. See [Export workspace data](/docs/data-handling#export-workspace-data).

### POST /v1/data/delete

Owner only, signed in to the dashboard: an API key gets `403 HUMAN_APPROVAL_REQUIRED` with the link. Permanently deletes the workspace's stored data. The workspace, its members and keys stay. A denial on a commit keeps holding that commit, with the person's name and email removed.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `confirm` | string | Yes | The workspace name, exactly |

Returns `{"deleted": {...}}` with the number of records removed for each kind of data.

## Workspace identity management

All routes below require an active, person-bound key belonging to an owner or admin of the selected workspace and the same feature access as the dashboard. Anonymous workspace keys and read-only roles cannot manage identity settings. Records are scoped to that workspace.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/identity/domains` | `{ "domains": [...] }` with ID, domain, `txt_name`, `txt_value`, `verified_at`, and `created_at` |
| POST | `/v1/identity/domains` | Send `{ "domain": "example.com" }`; returns 201 |
| POST | `/v1/identity/domains/{id}/verify` | Verify the published DNS TXT record; returns 204 only after ownership is verified |
| DELETE | `/v1/identity/domains/{id}` | Remove the domain; returns 204 |
| GET | `/v1/identity/scim/tokens` | `{ "tokens": [...] }` containing ID, label, prefix, creation and last-use timestamps; no secrets |
| POST | `/v1/identity/scim/tokens` | Send `{ "label": "Okta" }`; returns 201 with `{ "token": "..." }` exactly once |
| DELETE | `/v1/identity/scim/tokens/{id}` | Revoke the token; returns 204 |

Missing or another workspace's IDs return 404. Invalid domains, labels and failed DNS verification return 400. SSO connection configuration and SCIM group-role mapping are available below.


| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/identity/sso` | Secret-free settings, saved-secret indicator, OIDC callback URL and SAML service-provider URLs |
| PUT | `/v1/identity/sso` | Full settings JSON; validates OIDC discovery or SAML certificate and verified domains before enforcement; returns 204. Setting up or changing the provider, turning off or no longer requiring single sign-on, and a default role above `member`, need an owner signed in to the dashboard; an API key gets `403 HUMAN_APPROVAL_REQUIRED` |
| DELETE | `/v1/identity/sso` | Remove the workspace's provider configuration; needs an owner; returns 204 |
| POST | `/v1/identity/sso/unlink` | Send `{"email": "..."}` to clear a member's identity provider link, so they link again on their next sign-in; clearing an owner's link needs an owner; returns 204 |
| GET | `/v1/identity/scim/groups` | `{ "groups": [...] }` with IDs, display names and mapped roles |
| PUT | `/v1/identity/scim/groups/{id}/role` | Send `{ "role": "viewer" }`, or an empty string to clear; returns 204 after synchronizing roles |

SSO settings accept `protocol`, `enabled`, `enforced`, `jit`, `default_role`, `issuer`, `client_id`, `client_secret`, `entity_id`, `sso_url`, and `certificate`. Secrets are accepted only as request-body data and never included in a settings response. An omitted or empty OIDC client secret preserves the current secret. The API validates the same state transitions as the dashboard; it does not accept a caller's claim that a domain or identity provider was verified. Reading or modifying configuration does not create an SSO session or identity-provider proof. SSO routes require SSO feature access; groups require SCIM, and role mapping additionally requires the roles feature. Owner and admin permissions apply to every route.

### IdP-managed MCP access

> **Beta, early access.** Flag: `idp_managed_mcp`, plus `sso`. Until your workspace has access, these routes answer `403 FEATURE_EARLY_ACCESS` with a link to request it.

Your identity provider decides who can use the MCP server, and MCP clients trade an ID-JAG for an access token with no Bumpkit sign-in. See [IdP-managed MCP access](/docs/idp-managed-mcp). Owners and admins only; responses are never cached.

| Method | Route | Result |
| --- | --- | --- |
| GET | `/v1/identity/sso/managed-mcp` | `enabled`, `required`, `tenant`, `role_domain`, `issuer`, `audience`, `resource`, `token_endpoint`, `aud_tenant` (the workspace ID), `scopes` and `clients` (`client_id`, `name`, `secret_prefix`, `created_at`, `last_used_at`), without secrets |
| PUT | `/v1/identity/sso/managed-mcp` | Replaces all four settings, so send every one you want to keep. Changing the tenant or Athenz domain, turning it off, or no longer requiring it needs an owner signed in to the dashboard; an API key gets `403 HUMAN_APPROVAL_REQUIRED`. Send `{"enabled": true, "required": false}`, optionally with `tenant` (the identity provider tenant ID-JAGs must name, for multi-tenant issuers) and `role_domain` (the Athenz domain whose roles map to scopes); returns 204. Turning it on needs OIDC single sign-on turned on; requiring it needs it turned on and a verified domain. Turning it off works without single sign-on |
| POST | `/v1/identity/sso/managed-mcp/clients` | Send `{"name": "Claude"}` (up to 64 characters), optionally with `client_id`, the ID your identity provider puts in ID-JAGs (up to 255 of `A-Z a-z 0-9 . _ : / @ + ~ -`; 409 if it's already registered in this workspace). Without it Bumpkit generates `bkmcp_ema_...`. Returns 201 with `client_id`, `name`, `secret_prefix`, `created_at` and `client_secret`, shown once |
| DELETE | `/v1/identity/sso/managed-mcp/clients/{id}` | Remove the client and stop every token issued to it; returns 204, or 404 for an unknown client |
| POST | `/v1/identity/sso/managed-mcp/test` | Send `{"assertion": "<ID-JAG>"}`; returns `accepted`, with `error` and `hint` when Bumpkit would refuse it, and what it read: `issuer`, `client_id`, `subject`, `member`, `role`, `scope`, `creates_member` and `expires_at`. The ID-JAG isn't used up |

MCP clients trade the ID-JAG at `POST https://bumpkit.dev/oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, `assertion`, and the client ID and secret by HTTP Basic or in the form. The response has `access_token`, `token_type`, `expires_in` (at most `3600`) and `scope`, with no refresh token. Errors and fixes: [Troubleshooting](/docs/idp-managed-mcp#troubleshooting).
