# SSO, SCIM & Roles

Managing members by hand doesn't scale and leaves former staff with access; SSO, SCIM and roles tie Bumpkit to your identity provider, for IT and security administrators.

This page covers how people sign in to the Bumpkit dashboard, how you add and remove members, and what each member can do.

Roles, single sign-on and SCIM are Beta. Beta features are in early access: until your workspace is on the list, the feature's dashboard page or tab shows **Sign me up for early access**, and the API answers `403 FEATURE_EARLY_ACCESS` with a link to `https://dashboard.bumpkit.dev/early-access?feature=<key>`. Signing up is per workspace and feature. The person who asked gets an email when it's on.

## Sign in with GitHub

The login page shows **Continue with GitHub**.

GitHub sign-in uses your primary, verified GitHub email. If a Bumpkit account already has that email, GitHub is linked to it. Otherwise Bumpkit creates a new workspace with you as its owner. Accounts created this way have no password until you set one.

## Team members and invitations

Workspaces can invite unlimited teammates. A person can belong to multiple workspaces. Single sign-on and SCIM provisioning are Beta, and Bumpkit turns them on per workspace through early access.

Invite, list and remove members from the dashboard **Team** page or the CLI:

```bash
bumpkit team invite alex@example.com
bumpkit team list
bumpkit team cancel INVITATION_ID
bumpkit team remove MEMBER_ID --yes
```

- The invitee gets an email with a link to `https://dashboard.bumpkit.dev/invite/<token>`. The link expires after 7 days. Bumpkit stores only a hash of the token.
- If the email doesn't send, the invitation still exists; copy the link from the Team page.
- Removing a member keeps them out of first sign-in and IdP-managed access until you invite them again or provision them through SCIM. A canceled or expired invitation doesn't bring them back.
- Removing a member removes their access to this workspace, cancels their pending invitations here, and revokes their keys for this workspace. Their account and access to other workspaces remain available. The audit log records it as `key.revoke`, for example `2 API keys of ada@acme.com`.
- You can't remove yourself.

The same operations are available over the API as `GET /v1/team`, `POST /v1/team/invitations`, `DELETE /v1/team/invitations/{id}` and `DELETE /v1/team/members/{id}`. Every change is recorded in the workspace audit log. See the [API Reference](/docs/api-reference) and [CLI Reference](/docs/cli).

Without roles enabled, a workspace has two roles: `owner`, who manages the team, settings and billing, and `member`. Only owners invite, cancel invitations and remove members. With [roles](#roles), admins can too.

### Create another workspace

Anyone signed in can create another workspace, for example a separate demo for Acme. You become its owner and keep your access to every other workspace. Each workspace has its own repositories, team, audit log and 60 free credits a month.

- **Dashboard:** open the workspace menu at the top of the sidebar and choose **Create workspace**. Name it, and Bumpkit switches you into it and opens repository setup.
- **CLI:** `bumpkit workspace create "Acme Demo"` prints the new workspace ID. Your API key stays bound to its current workspace, so run `bumpkit login` and choose the new workspace to use it, or add `--switch` to start that sign-in right away. `bumpkit workspace list` shows every workspace you belong to.
- **MCP:** the `create_workspace` tool takes the name and `confirm: true`.
- **API:** `POST /v1/workspaces` and `GET /v1/workspaces`.

You can create up to 10 workspaces in 24 hours. The new workspace's audit log records the creation as `workspace.create`.

## Roles

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

With roles enabled, every member has one of seven roles.

| Role | Key | What it can do |
|---|---|---|
| Owner | `owner` | Everything, including billing and deleting the workspace |
| Admin | `org_admin` | Manages members, roles, SSO, SCIM, integrations and settings; reads the audit log, exports evidence, overrides the gate and manages policy |
| Policy admin | `policy_admin` | Approves policy rules, overrides the release gate and reviews overrides, plus everything a member can do |
| Approver | `approver` | Reviews release gate overrides, plus everything a member can do |
| Member | `member` | Uses Bumpkit: API keys, checks, releases and incidents |
| Viewer | `viewer` | Read-only access to the dashboard |
| Auditor | `auditor` | Read-only, plus the audit log and evidence export |

| Permission | Owner | Admin | Policy admin | Approver | Member | Viewer | Auditor |
|---|---|---|---|---|---|---|---|
| Delete workspace, manage billing | Yes | | | | | | |
| Manage workspace settings, team, roles, integrations, SSO | Yes | Yes | | | | | |
| Read audit log, export evidence | Yes | Yes | | | | | Yes |
| Override the gate, manage policy | Yes | Yes | Yes | | | | |
| Review gate overrides | Yes | Yes | Yes | Yes | | | |
| Manage API keys, resolve incidents, make changes | Yes | Yes | Yes | Yes | Yes | | |

Viewers and auditors can't make changes in the dashboard; write actions return a read-only message.

Evidence exports (`GET /v1/evidence`, `/v1/evidence/signed`, `/v1/evidence/governance`) and audit event exports (`GET /v1/audit/events`) need an API key created by an owner, admin or auditor. Any other role, and keys not tied to a person such as CI keys, get `403 ROLE_REQUIRED`, and the refusal is written to the audit log as `evidence.export.denied` or `audit.read.denied`. Export evidence in CI with a key an auditor created.

### Change a member's role

Granting a role above `member` needs a person signed in to the dashboard. An API key, the CLI or an agent can lower a role or set it to `member`.

Owners and admins change roles on the **Team** page. To lower a role with the CLI or over the API:

```bash
bumpkit team role MEMBER_ID viewer
```

```bash
curl -X PATCH https://bumpkit.dev/v1/team/members/MEMBER_ID \
  -H "x-api-key: $BUMPKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "viewer"}'
```

- You can't change your own role.
- Only an owner can make someone an owner or change another owner's role.
- Moving a member to `viewer` or `auditor` revokes their API keys, recorded in the audit log as `key.revoke`.

### Invite with a role

`bumpkit team invite` takes `--role` (default `member`), and `POST /v1/team/invitations` accepts `{"email": "...", "role": "..."}`. An API key, the CLI or an agent can invite as `member` or `viewer`, and never receives an invitation link. A person signed in to the dashboard grants anything higher. Only owners and admins can invite with a role other than `member`, and only owners can invite an owner. Without the `roles` flag, a role other than `member` is rejected.

## Single sign-on

Setting up or changing the identity provider, turning single sign-on off or removing it, setting a default role above `member`, changing the IdP-managed MCP tenant or Athenz domain, turning that access off, and creating SCIM tokens all need a person signed in to the dashboard. An API key, the CLI or an agent gets `HUMAN_APPROVAL_REQUIRED`.

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

Each workspace can connect one identity provider over OpenID Connect or SAML 2.0. Owners and admins manage it in **Settings → Single sign-on** (`/settings/sso`). Admins can change sign-in details such as the default role, the client secret and whether accounts are created on first sign-in. Only an owner can set up or change the identity provider, turn off single sign-on or stop requiring it, or remove it. Every change is recorded in the workspace audit log.

Members sign in at `/login/sso` with their work email. Bumpkit finds the workspace from the email domain and redirects to its identity provider.

### 1. Verify your email domains

SSO only links people by email on domains you've verified, and SCIM only accepts emails on them. Under **Verified domains**, add a domain such as `example.com`, then publish the TXT record Bumpkit shows:

```text
_bumpkit-challenge.example.com  TXT  "bumpkit-domain-verification=<token>"
```

Click **Verify** once the record resolves. A domain can be verified by only one workspace.

### 2. Configure OIDC

Register Bumpkit as a web application (authorization code flow) in your identity provider:

| Setting | Value |
|---|---|
| Redirect URI | `https://dashboard.bumpkit.dev/auth/sso/oidc/callback` |
| Scopes | `openid email profile` |

Then fill in Bumpkit's form:

| Field | Description |
|---|---|
| OIDC issuer URL | `https` issuer, for example `https://login.example.com`. Bumpkit reads `<issuer>/.well-known/openid-configuration`, and its `issuer` must match exactly |
| OIDC client ID | Client ID from your identity provider |
| OIDC client secret | Encrypted with AES-256-GCM before it's stored and never shown again; leave blank to keep the saved one |

Bumpkit uses PKCE (`S256`) and a nonce, authenticates to the token endpoint with HTTP Basic (or `client_secret_post` when that's the only method the provider lists), and verifies the ID token signature with the provider's JWKS. Supported signing algorithms are `RS256`, `RS384`, `RS512`, `PS256`, `ES256` and `ES384`.

The ID token must carry `sub`. It also needs `email` the first time, to link the person to a member. With `email_verified: false`, the email can't be used to link, so the sign-in is refused unless the account is already linked. `name` becomes the member's display name.

### 3. Or configure SAML

Bumpkit is the service provider. Give your identity provider these values from the settings page:

| Setting | Value |
|---|---|
| Entity ID (audience) | `https://dashboard.bumpkit.dev/auth/sso/saml/<connection-id>` |
| ACS URL (HTTP-POST) | `https://dashboard.bumpkit.dev/auth/sso/saml/acs` |
| Metadata | `https://dashboard.bumpkit.dev/auth/sso/saml/<connection-id>/metadata` |
| NameID format | `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress` |

The connection ID appears after you first save the connection. Then fill in Bumpkit's form:

| Field | Description |
|---|---|
| SAML identity provider entity ID | Your identity provider's issuer |
| SAML SSO URL | `https` URL that receives the authentication request (HTTP-Redirect) |
| SAML signing certificate (PEM) | The identity provider's X.509 signing certificate |

Responses must answer a sign-in that started in Bumpkit (IdP-initiated sign-in isn't supported), carry exactly one signed assertion, and not be encrypted. Each assertion is accepted once.

Bumpkit reads these attributes, matching names case-insensitively:

| Value | Attribute names |
|---|---|
| Email | `email`, `mail`, `emailaddress`, `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`, `urn:oid:0.9.2342.19200300.100.1.3` |
| Display name | `name`, `displayname`, `http://schemas.microsoft.com/identity/claims/displayname`, `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name`, `urn:oid:2.16.840.1.113730.3.1.241` |

If no email attribute is present, the NameID is used when it is an email address.

### 4. Choose how accounts are created

| Option | Effect |
|---|---|
| **Role for new members** | Role given to accounts created on first sign-in or over SCIM; any role except owner (default `member`) |
| **Create accounts on first sign-in** | Just-in-time provisioning (on by default). When off, only existing members can sign in |
| **Turn on single sign-on** | Enables the connection |
| **Require SSO for verified domains** | Enforces SSO; needs SSO turned on and at least one verified domain |

On first SSO sign-in, Bumpkit links an existing account with the same email in this workspace. An email that belongs to another workspace is refused. With just-in-time provisioning on, a new person gets an account and joins the workspace on first sign-in; there's no member limit.

### Enforced SSO and break-glass

When SSO is enforced, members with a verified-domain email can't sign in with a password or GitHub, and new accounts on those domains can't be created through GitHub.

Owners keep a break-glass sign-in with their password at `/login/break-glass`, for when the identity provider is down. Every break-glass sign-in is recorded in the audit log as `sso.break_glass`.

The first sign-in links a member to their identity provider account, and that link never moves to another account. Bumpkit finds the member by that link first. Otherwise it matches by email, and the email must be verified and on one of your verified domains. A linked member keeps signing in if their email domain is later removed or unverified, as long as they start at `/login/sso` with an email on a domain that's still verified; deactivate them or clear their link to stop that. A deactivated member is refused before anything else is checked. Dashboard sign-in and [IdP-managed MCP access](/docs/idp-managed-mcp) follow the same rule. A sign-in with the same email from a different identity provider account is refused and recorded as `sso.relink_refused`; to let it through, clear their link in **Settings → Single sign-on**, with `bumpkit identity sso unlink EMAIL --yes`, or with the `identity_sso_unlink` MCP tool. They link again by verified email on their next sign-in. Only an owner can clear another owner's link. A workspace owner's email can't be changed over SCIM. Changing the issuer, client ID, protocol or SAML entity ID clears every link and turns off IdP-managed access, along with its tenant and Athenz domain. Changing that tenant clears every link too.

While single sign-on or IdP-managed access is required, only an owner can remove a verified domain, and the last one can't be removed. While single sign-on is on, members on a verified domain can't change their email in account settings, and nobody can change theirs to that domain. Removing and setting up single sign-on again doesn't lock anyone out.

If your issuer serves several tenants and you set an **Identity provider tenant**, its ID tokens must carry a matching `tenant` claim, or dashboard sign-in is refused.

SSO endpoints are rate-limited per IP; too many attempts return `429` with `Retry-After: 60`.

### MCP access through your identity provider

With an OIDC connection, your identity provider can also decide who uses Bumpkit from MCP clients like Claude and Codex, with no Bumpkit sign-in or consent screen. See [IdP-managed MCP access](/docs/idp-managed-mcp).

## Sign-in security

Password sign-in, sign-up and password reset are protected for every workspace, with no flag to turn on.

### Failed sign-ins

- After a few failed attempts for an email address, each new attempt has to wait a little longer, and repeated failures pause sign-in for that address for a short time.
- The same rules apply to addresses with no account, so the response never reveals whether an account exists.
- A browser that has signed in to the account before, or that completed a password reset, gets past the pause. A completed reset also lifts it.
- Pausing a real account is recorded in the audit log as `auth.login_locked`.

Throttled requests get `429` with `Retry-After` in seconds and a page that says how long to wait.

### Limits per network

Sign-in, sign-up, password reset and confirmation requests are also limited per network, and sign-up and reset emails are capped per address. Past a limit you get `429` with `Retry-After`.

### Password reset

**Forgot password?** on the sign-in page leads to `/password/forgot`. The answer is always the same, whether or not the account exists. The email holds a single-use link that expires in 30 minutes; Bumpkit stores only a hash of its token, and using one link retires every other open link for that account. The new password follows the sign-up rules (8 to 72 characters). A successful reset signs out every session for the account, emails a confirmation, and is recorded as `auth.password_reset_requested` and `auth.password_reset`.

### Change your sign-in email

Under **Settings → Your email address**, enter the new address and your password. Your email doesn't change yet. Bumpkit sends a single-use link to the new address that expires in an hour and stores only a hash of its token, and tells your current address that a change was requested, without the link. **Settings** shows the change as waiting for confirmation, with a button to cancel it. Asking again replaces the earlier link.

The link only works while you're signed in to the same account. When you open it and confirm, Bumpkit checks again that the address is free and that no identity provider manages its domain or your current one. It then changes your email, signs out every other session, retires open password reset links, and tells your old address. A password reset cancels a waiting change.

Requests are limited per account and per new address, and past a limit the page tells you to try again later. If the confirmation email can't be sent, your email doesn't change. Each step is recorded as `auth.email_change_requested`, `auth.email_change_canceled` or `auth.email_changed`. There's no API, CLI or MCP path for this: it needs your password and the new mailbox. Addresses set by your identity provider over SCIM don't go through this flow.

When a workspace enforces SSO, members get an email pointing them to `/login/sso` instead of a reset link, and links issued before enforcement stop working. The owner can still reset the break-glass password. Accounts that sign in only with GitHub or SSO get an email explaining how they sign in.

### Verified sign-up

Signing up with an email and password sends a confirmation link that works once and expires in 24 hours. The account is created only when you open the link and choose **Create my account**, so a mail scanner that opens links can't create one. The answer on the sign-up page is the same whether or not the address already has an account; an existing account gets an email with a sign-in and reset link instead. GitHub, GitLab, SSO and invitation sign-ups don't need this step.

### Cross-site request protection

The dashboard refuses any non-GET request that doesn't come from the dashboard itself. When the browser sends `Sec-Fetch-Site`, only `same-origin` is accepted; otherwise a present `Origin` must match. The SAML ACS endpoint is the one exception, because the identity provider posts to it.

## SCIM provisioning

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

Bumpkit implements SCIM 2.0 Users and Groups, so your identity provider can create, update and deactivate members and map groups to roles.

### Connect your identity provider

1. Verify your email domains (see above). SCIM only accepts users on verified domains.
2. In **Settings → Single sign-on**, under SCIM, create a token with a label such as `Okta`. Copy it; it starts with `bk_scim_` and is shown once. Bumpkit stores only its hash.
3. In your identity provider, set:

| Setting | Value |
|---|---|
| Base URL | `https://bumpkit.dev/scim/v2` |
| Authentication | Bearer token: `Authorization: Bearer bk_scim_...` |
| Unique identifier | `userName` (the person's email) |

Revoke a token from the same page. Token creation and revocation are audited.

### Endpoints

| Method | Path | Operation |
|---|---|---|
| `GET` | `/scim/v2/ServiceProviderConfig` | Capabilities |
| `GET` | `/scim/v2/ResourceTypes` | `User` and `Group` |
| `GET` | `/scim/v2/Schemas` | Schemas |
| `GET` | `/scim/v2/Users` | List users; filter `userName`, `externalId` or `emails.value` with `eq` |
| `POST` | `/scim/v2/Users` | Create a user |
| `GET` | `/scim/v2/Users/{id}` | Get a user |
| `PUT` | `/scim/v2/Users/{id}` | Replace a user |
| `PATCH` | `/scim/v2/Users/{id}` | `add` or `replace` on `active`, `userName`, `externalId`, `displayName`, `name.formatted`; `remove` on `externalId` and `displayName` |
| `DELETE` | `/scim/v2/Users/{id}` | Deactivate a user |
| `GET` | `/scim/v2/Groups` | List groups; filter `displayName` with `eq` |
| `POST` | `/scim/v2/Groups` | Create a group |
| `GET` | `/scim/v2/Groups/{id}` | Get a group |
| `PUT` | `/scim/v2/Groups/{id}` | Replace a group |
| `PATCH` | `/scim/v2/Groups/{id}` | Change `displayName` or `members` |
| `DELETE` | `/scim/v2/Groups/{id}` | Delete a group |

Filters support only `<attribute> eq "<value>"`. Pages return at most 200 results. Bulk, sort, ETags and password changes aren't supported. Errors use the SCIM error schema.

### Users

- `userName` must be an email on a verified domain.
- New users get the connection's **Role for new members**, or `member` without an SSO connection. There's no member limit.
- Setting `active` to `false`, or `DELETE`, deactivates the member instead of deleting them: they can't sign in, existing sessions stop working, and their API keys are revoked (audited as `key.revoke`, with the SCIM token's label as the actor).
- Owners can't be deactivated over SCIM; change their role in Bumpkit first.

### Groups to roles

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

Mapping groups to roles needs both the `scim` and `roles` flags; with `roles` off, the mapping can't be changed. Groups your identity provider pushes appear under **Groups to roles**. Map each group to a role, or to no role. A member's role is then the highest-ranked role among their mapped groups, in this order: `org_admin`, `policy_admin`, `approver`, `member`, `auditor`, `viewer`. A member in no mapped group gets the connection's role for new members, or `member`. Owners are never changed by group sync. Whenever group sync leaves a member as `viewer` or `auditor`, including after a group is deleted or their membership changes, their API keys are revoked, recorded as `key.revoke`.

### Rate limits

Each token may make 10 requests per second with bursts of 100; beyond that Bumpkit returns `429` with `Retry-After: 1`. After 20 failed authentications in a minute from one IP, that IP gets `429` with `Retry-After: 60`.
