Skip to content
GitmoapdocsBeta
Concept· Why does it work like that?Draft

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:

json
{
"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

CredentialLooks likeActs asScopes
Personal access tokengmp_…youthe ones you chose when you minted it
Service-account tokengms_…a service account belonging to a group or repositorythe ones chosen when it was minted
Signed-in session — the web app, or gitmoap login—youevery 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 resource

A 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.

BundleWhat it expands to
read_apievery scope whose verb is read
write_apievery scope, except those no bundle carries
read_repositorythe :read scopes of the repository domains — code, branches, tags, releases, commits, diffs, merge requests, reviews, rules, pipelines and work items
write_repositoryevery scope of those same domains
mcp_agentthe reads an AI agent needs through the MCP server, plus feedback:create
mcp_agent_writemcp_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:

ScopeWhat it allows
reviewer:reviewapproving, rejecting and withdrawing a merge-request review
pipeline:approvegranting or denying a pipeline step that waits for approval
ai_org:approvepublishing an AI organization's revisions, and resuming what was paused
access:administerlimiting access, and stopping a group or repository inheriting it
role:managedefining, 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.