Token scopes
Overview
Every operation in the API names the one scope a token must carry to call it, so you can give a script, a build or an AI agent exactly the operations its job needs and nothing more. A token without that scope is refused before anything else is looked at, and the refusal names the scope it was missing.
A scope only ever narrows a token. Whether a request then succeeds still depends on the access level the token's owner holds on the group or repository it touches — a token is never a promotion.
How it works
One scope per operation
A scope is spelled domain:verb: repo:read, branch:create, merge_request:merge. The split is deliberately fine, so a token that opens merge requests does not thereby merge them. Each operation in the API reference shows the scope it needs; one marked public needs no token at all.
A token that lacks the scope gets 403 with the scope named, and the check comes before the resource is looked up, so the answer is the same whether or not the thing you asked for exists:
{ "error": { "code": "INSUFFICIENT_SCOPE", "message": "This token is missing the required scope `branch:create`", "required_scope": "branch:create" }}Scopes are fixed when a token is minted. To add one, mint a replacement and revoke the old token.
Which credentials carry scopes
| Credential | Looks like | Acts as | Scopes |
|---|---|---|---|
| Personal access token | gmp_… | you | the ones you chose when you minted it |
| Service-account token | gms_… | a service account belonging to a group or repository | the ones chosen when it was minted |
Signed-in session — the web app, or gitmoap login | — | you | every scope; your access level is the only limit |
An AI app you connect by signing in receives a personal access token carrying exactly the scopes you approved, and nothing else.
Three checks, in order
request carrying a token ├─ does the token carry this operation's scope? no → 403 INSUFFICIENT_SCOPE ├─ is the target inside the token's binding? no → 403 FORBIDDEN └─ does the owner's access level allow the action? no → 403 FORBIDDEN, or 404 on a private resourceA Reporter's token minted with git:write still cannot push: the scope and the access level both have to agree.
Bundles
Instead of listing scopes you can name a bundle. It is expanded into individual scopes at the moment you mint the token, and the expansion is what is stored — a scope added to the API later is not added to a token you already hold.
| Bundle | What it expands to |
|---|---|
read_api | every scope whose verb is read |
write_api | every scope, except those no bundle carries |
read_repository | the :read scopes of the repository domains — code, branches, tags, releases, commits, diffs, merge requests, reviews, rules, pipelines and work items |
write_repository | every scope of those same domains |
mcp_agent | the reads an AI agent needs through the MCP server, plus feedback:create |
mcp_agent_write | mcp_agent, plus opening, changing and commenting on work items and merge requests, reacting, requesting reviewers and creating branches |
Access tokens lists exactly what each bundle leaves out, including git, which is a domain of its own.
Scopes no bundle carries
Five scopes are never part of any bundle, write_api included. A token carries one only when you name it:
| Scope | What it allows |
|---|---|
reviewer:review | approving, rejecting and withdrawing a merge-request review |
pipeline:approve | granting or denying a pipeline step that waits for approval |
ai_org:approve | publishing an AI organization's revisions, and resuming what was paused |
access:administer | limiting access, and stopping a group or repository inheriting it |
role:manage | defining, changing and deleting an organization's custom roles |
The first three are approvals, which are only worth something because a person chose to give them; a token minted for automation should not be able to approve by accident. The last two change what everyone beneath a group can do.
Binding a token to one group or repository
A token can also be bound to a single group or a single repository when it is minted. A repository binding covers that repository alone. A group binding covers the group and everything beneath it, including repositories created after the token was minted. A request outside the binding answers 403 FORBIDDEN, even against a public repository anyone could read without a token.
You can only bind to something you can already see. Calls that name no group or repository — your own profile, your own token list — are governed by scope alone, which is why token:manage deserves the most care: it can mint a further token with any scopes and no binding.
Related
- Access tokensminting, binding, rotating and revoking a token, and every scope a common job needs
- Service accountsthe bot identities
gms_tokens act as - Connect an AI app by signing inhow an app receives a token with only the scopes you approve
- Errorsthe shape of
INSUFFICIENT_SCOPEand every other refusal - AI agents and the MCP serverthe
mcp_agentbundles in use