Reference

Version 4 · Updated

OAuth apps

Apps that act for people with their consent, or installed in an organization as themselves; scopes, tokens and MCP sign-in.

An OAuth app lets another service, such as a CI system, a chat bot, an agent platform or an IDE, work with Branchscope without anyone pasting a token into it. People approve what it may do on a consent page and can take that back in one click. Organizations can also install an app, so it acts as itself.

An app works in one of two ways:

  • For a person: someone signs in through the app and approves it. The app then acts as them, limited to the scopes and repositories they approved.
  • Installed in an organization: an owner installs it on some or all of the organization's repositories, and it acts as its own bot.

Either way, Branchscope treats the app as an agent named app:<slug>. Like every agent, it can't update branches whose rulesets block agents, such as main, unless its own app is on their bypass list; a person merges. Everything it does is shown as its own in activity, changes and webhooks.

Registering an app#

Under Developer apps (in your account menu), register an app for yourself or for an organization you own. It has:

  • a name, a description and a homepage, shown to people on the consent page;
  • a slug, which can't change: it names the app in activity (app:<slug>) and its bot (app-<slug>);
  • redirect URIs: https://, or http://127.0.0.1 or http://localhost with any port for apps on the person's own machine;
  • the scopes it may ask for;
  • an optional webhook URL, which gets the events of every repository the app is installed in;
  • an optional icon: PNG, JPEG or WebP up to 256 KB. Branchscope stores and serves it, so the consent page never loads anything from elsewhere. Without one, the app gets a generated icon.

You get a client ID and, for a confidential app, a client secret (bsc_cs_…), and a webhook secret if you gave a webhook URL. Secrets are shown once; New client secret replaces the client secret at once.

Tick Public client for an app in a browser or on a desktop, which can't keep a secret. A public client has no secret and signs in with PKCE alone. Public clients can't be installed in organizations.

Scopes#

ScopeAllows
repo:readrepositories, files, history, changes and activity
repo:writecreating repositories, pushing branches over git, starting graph builds
changes:writeopening, closing, reopening and merging changes, re-running reviews
reviews:readreviews' diagrams and checks
graphs:readcode graphs and views
webhooks:adminthe webhooks of repositories and organizations the person manages
org:readorganizations and their members
checks:writereporting checks on commits, as CI does
deployments:writerecording deployments and their statuses, and Deploy again

Every API endpoint, MCP tool and git operation checks its scope. An app gets 403 with the scope it lacks, such as this app wasn't granted changes:write. Some things are never open to apps, whatever their scopes: tokens, SSH keys, organization membership, repository settings, rulesets, environments' protection, secrets, mirrors and collaborators, imports, the push guard, and other apps. Those stay with a person. Apps, like every agent, only draft releases; a person publishes them.

Acting for a person#

Branchscope is an OAuth 2.1 authorization server. The flow is the authorization code flow with PKCE (S256), required for every app.

  1. Send the person to the consent page:
https://<web host>/oauth/authorize?response_type=code&client_id=app_…&redirect_uri=https://ci.example.com/callback
  &scope=repo:read%20changes:write&state=…&code_challenge=…&code_challenge_method=S256
  1. They sign in if needed, see the app, its owner and the scopes, choose all their repositories or a list, and press Authorize. Branchscope sends them back to your redirect URI with code and state (and iss). Cancel sends back error=access_denied.
  2. Within a minute, trade the code for tokens:
curl -u "$CLIENT_ID:$CLIENT_SECRET" https://<api host>/oauth/token \
  -d grant_type=authorization_code -d code=… -d redirect_uri=https://ci.example.com/callback -d code_verifier=…

The answer has an access token (bsc_oat_…, valid for an hour) and a refresh token (bsc_ort_…, valid for 30 days). Use the access token like any other: Authorization: Bearer bsc_oat_… for the API, MCP and git over HTTP.

Refreshing: grant_type=refresh_token gives a new pair. Each refresh token works once. If an old one is used again, Branchscope takes it as stolen and revokes the whole authorization, and the person has to approve the app again.

Revoking: an app can end a token itself at POST /oauth/revoke (RFC 7009). Revoking a refresh token ends the whole authorization.

Discovery documents for libraries that read them are at https://<api host>/.well-known/oauth-authorization-server (RFC 8414).

Authorized apps#

Under Authorized apps (in your account menu) you see each app you've approved: its scopes, repositories and last use. Revoke ends all its access at once. A site administrator's powers never pass to an app they authorize; it gets their ordinary access only.

Installing in an organization#

An organization owner installs an app from its page (/apps/<slug>), choosing the organization and all of its repositories or a list. Owners see installed apps on the organization's page, and remove them there. Installing again from the app's page changes the repositories.

An installed app acts as its bot, app-<slug>. The bot can't sign in, has no namespace and owns nothing. It can write to the installation's repositories, within the app's scopes, but never administer them.

To act, the app gets an installation token with its client ID and secret:

curl -u "$CLIENT_ID:$CLIENT_SECRET" https://<api host>/oauth/token \
  -d grant_type=client_credentials -d installation_id=42

The token (bsc_ait_…) is valid for an hour and covers that installation only. There's no refresh token: ask for a new one when it runs out. Removing the installation ends its tokens at once.

Agent apps#

Tick Agent app in an app's settings to make it one. An organization that installs an agent app can:

  • hand it tasks, on a change ("Ask an agent") or free-standing from a branch. It hears of each at its webhook (agent.task) and in its MCP sessions, and reports on it (see Agents);
  • limit how many changes it has open at once (10 by default); opening one more is refused;
  • pause it: its installation tokens stop working at once and its MCP sessions end, and it can't get new tokens until it's resumed. Its tasks wait.

The organization's Agents page shows each agent app: its open tasks, recent changes, what the push guard refused it, and its MCP sessions, with the limit and Pause beside it.

AI provider keys#

An installed app can run on the team's own key for Anthropic, OpenAI, Mistral or another provider, instead of its own, so the team pays the provider directly and sees what the app spends.

Adding one: under Settings → Secrets → AI provider keys, a repository's administrators (for that repository) or an organization's owners (for every repository the app is installed on) choose the app, from those installed there, and the provider, paste the key and may set a monthly cap in euros. The form links to each provider's API keys page and to its own spending limits: set a limit there too, since that's the one it bills by. Once saved, a key can't be read back, only replaced or deleted. A repository's key for an app wins over its organization's.

From the CLI, with the key read from standard input. --cap is in euros (none for no cap), and on its own changes only the cap:

bscope ai-key set reviewer anthropic -R acme/web --cap 50 < anthropic-key.txt
bscope ai-key set reviewer anthropic -R acme/web --cap 80
bscope ai-key list --org acme
bscope ai-key remove reviewer anthropic -R acme/web

Only the app it's for gets it. The app asks with its installation token, naming the repository it's working on (or null for the organization's key):

curl -X POST https://<api host>/v1/installations/ai-keys/anthropic \
  -H "Authorization: Bearer $INSTALLATION_TOKEN" -H "Content-Type: application/json" \
  -d '{"repository": "acme/web"}'
{ "provider": "anthropic", "key": "sk-ant-…", "level": "repository", "month": "2026-10", "spentCents": 1840, "capCents": 5000, "remainingCents": 3160 }

People, agents signed in with the CLI, an app acting for a person and every other app are refused (403), as is a repository the installation doesn't cover; without a key for this app and provider the answer is 404. Use the key for the piece of work at hand and don't store it: ask again next time, so a replaced or deleted key, a paused installation or a reached cap takes effect at once. Each hand-over is counted on the key, not logged one by one.

Reporting what it spent: Branchscope can't see the provider's billing, so the app reports its usage after each piece of work, in euro cents (converted from the provider's currency and rounded up):

curl -X POST https://<api host>/v1/installations/ai-keys/anthropic/usage \
  -H "Authorization: Bearer $INSTALLATION_TOKEN" -H "Content-Type: application/json" \
  -d '{"repository": "acme/web", "inputTokens": 120000, "outputTokens": 8000, "costCents": 54}'
{ "month": "2026-10", "spentCents": 1894, "capCents": 5000, "remainingCents": 3106 }

The cap: once the spend reported for the calendar month (UTC) reaches the cap, the key is refused (403, with spentCents and capCents in the error's details) until the next month or until an administrator raises the cap. Reports are still taken past the cap. The settings list each key with this month's reported spend against its cap, its tokens and when the app last got it. Setting, replacing or deleting a key, and changing its cap, are in the audit log, never with the key.

The Branchscope CLI#

Every Branchscope host has one built-in app, the Branchscope CLI. bscope agent login --name claude-code signs in an agent on your machine through it: the consent page names the agent, and its tokens act for you as that agent (claude-code, not app:…). You can sign in several agents, each its own authorization. They're listed, and revoked one by one, under Authorized apps.

The app's webhook#

If the app has a webhook URL, it gets every event of every repository it's installed in. Deliveries are signed with the app's webhook secret and work like any other webhook: retries, pausing and the delivery log, under Webhook deliveries on the app's settings. Each delivery also says which installation it came through:

"installation": { "id": 42, "org": "acme" }

Pass that id as installation_id to get a token for it.

MCP clients signing in#

MCP clients such as Claude Code can sign in through OAuth on their own:

  1. Without a token, /v1/mcp answers 401 with WWW-Authenticate: Bearer resource_metadata="…". It points at the endpoint's metadata (RFC 9728), which names Branchscope as the authorization server.
  2. The client registers itself at POST /oauth/register (RFC 7591).
  3. It opens the consent page in your browser. You approve it, and it connects with the token.

Clients that register themselves are always public (PKCE, no secret) and may only ask for repo:read, changes:write, reviews:read and graphs:read. No one vouches for them, so the consent page says the app registered itself: approve one only if you just started signing in from it. They can't be installed in organizations.