Skip to content
GitmoapdocsBeta
Guide· How do I…?

Access

Overview

Access is who can do what on a group or a repository. You give it with a grant: one person, service account or team, one role, one group or repository. What the role allows applies there and in everything beneath it, so giving someone Developer on a group lets them push to every repository inside it.

Four questions come up again and again, and each has one call: who has access here, and why; why can this person do this, or not; give these people this role here; and take it away. Each change can be previewed first, so you see exactly who gains or loses what before anything moves.

Quick start

Quick start3 steps

Give someone access to a repository, after seeing what it changes

You’ll need Maintainer on the repository.
  1. Preview it: gitmoap access create-repo-access-grants --repo-id acme/api --subjects '[{"principal":"<user-id>"}]' --role '{"builtin":"developer"}' --dry-run.

  2. Read the preview — each person listed gains the permissions shown, here and in everything beneath that inherits access.

  3. Run the same command without --dry-run to make the change.

Before you start

  • Maintainer on the group or repository to give, change or take away access there.And you can only give what you hold yourself: a Maintainer can make someone a Maintainer but not an Owner, and changing or removing someone's Owner role needs an Owner. A role you hold on a group above counts, as long as nothing between stops it.
  • Reporter on the group or repository to see who has access.A group or repository being public or internal does not make its list of people public — who has access is not what a scope publishes. The same goes for your organisation's people page: it needs Reporter on the top-level group.
  • Owner of the organisation to read its access review, because it lists access on groups and repositories other people may not be able to see.
  • You can always see why you have the access you haveon anything you can see, and always give up a grant you hold yourself, whatever you could otherwise manage.
  • A token with the right scope, if you are using the CLI or the API: access:read to list and explain, access:manage to give, change and revoke. Both come with the read_api and write_api bundles. access:administer does not — it is asked for by name.
  • The person, service account or team exists in the organisation.A service account can only be given access where it belongs: its own group or repository, or beneath it.

How it works

A grant is who, what and where. The who is a person, a service account or a team. The what is a role — one of the five built-in roles, Guest, Reporter, Developer, Maintainer and Owner, each holding everything the one below it holds, or a role your organisation defined. The where is a group or a repository. One subject holds at most one grant on one place; changing their role changes that grant.

Access flows down. A grant on a group reaches every subgroup and repository beneath it. Someone's access to a repository is everything their grants on it and on each group above it add up to — the highest role wins. A group or repository can stop access flowing into it from above; then only grants made on it, or beneath it, reach it, and the organisation's Owners always do.

The organisation's Owners always keep their access. Owner of the top-level group reaches everything in the organisation, whatever any group or repository beneath says. There is always at least one: the last Owner of an organisation cannot be removed or demoted until someone else is made one. Repositories and subgroups may be left without an Owner of their own, because the organisation's Owners manage them.

Visibility is separate from access. A public repository lets anyone read what it publishes — its code, its issues, its releases — and an internal one lets anyone signed in do the same. Visibility never lets anyone change anything, and never shows who has access.

Service accounts are kept close to home. An account holds access only in the group or repository it belongs to, or beneath it, and never as Owner. Its own level is changed on the account itself, not here.

Nobody gives what they do not hold. Every change is checked against your own access where the change is made: you can give or take away a role only if you hold everything it allows and it does not outrank you.

Every change is recorded. Giving, changing and revoking access each land in the organisation's audit log with the role before and after, not just the fact that something changed. A preview changes nothing, so it is not recorded.

How to

See who has access, and why

The list shows every grant that reaches the group or repository — made there, or inherited from a group above — oldest first, each with where it was made, who made it, why, and when it ends. The organisation's Owners are counted separately, because they reach everything anyway.

Open the repository, choose Settings, then Members, for the people given access to the repository directly. Teams, service accounts and access inherited from the groups above are listed through the CLI and the API.

Terminalbash
gitmoap access list-repo-access --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/access \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Open the group, choose Settings, then Members, for the people given access to the group directly. Teams, service accounts and access inherited from the groups above are listed through the CLI and the API.

Terminalbash
gitmoap access list-group-access --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/access \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Narrow the list with source (direct or inherited), subject_kind (user, service_account or team) or search.

Find out why someone can or cannot do something

Ask about yourself, or — with Reporter there — about anyone. The answer lists every grant that bears on them, how it reaches them (directly, or through which team), whether it still counts, and what any limit took away. Name one permission, such as code.push, to get a plain yes or no as well.

The web app has no screen for this; use the CLI or the API.

Terminalbash
gitmoap access explain-repo-access --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/access/explain \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The web app has no screen for this; use the CLI or the API.

Terminalbash
gitmoap access explain-group-access --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/access/explain \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Give access to people, service accounts or teams

Name up to 50 subjects and one role. They are all given access together, or none of them is. Someone who already has that exact role there is left as they are and listed as unchanged; someone who has a different role there is refused — change their existing grant instead.

Add expires_at for access that ends on its own, up to a year away, and reason so the next person reading the list knows why it exists.

Open the repository, choose Settings, then Members, then Add member, to give one person a built-in role. Service accounts, teams, expiry and reasons are set through the CLI and the API.

Terminalbash
gitmoap access create-repo-access-grants \
--repo-id <id> \
--role '<json>' \
--subjects '<json>'
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/access/grants \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "role": "<object>" }'

Open the group, choose Settings, then Members, then Add member, to give one person a built-in role. Service accounts, teams, expiry and reasons are set through the CLI and the API.

Terminalbash
gitmoap access create-group-access-grants \
--group-id <id> \
--role '<json>' \
--subjects '<json>'
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/access/grants \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "role": "<object>" }'

Preview a change before making it

Add dry_run=true — --dry-run in the CLI — to giving, changing or revoking access. Nothing is written. The answer lists each person whose access would change where the change is made, with the permissions they would gain and lose and their role before and after, and counts how many groups and repositories the change reaches because they inherit from there. For a team, each member is listed.

Note

A preview is computed from the facts as they stand the moment you ask. If someone else changes access in between, run it again before relying on it.

Change someone's role, or when their access ends

Change the role, the end date or the reason of one grant, by its id from the list. Clearing the end date makes the access last until it is revoked. You need to be able to give both the role they have and the role you are giving them.

Open Settings, then Members, and choose a new role from the dropdown on the person's row, for a person's direct grant. Expiry and reasons are changed through the CLI and the API.

Terminalbash
gitmoap access update-access-grant --grant-id <id>
curlbash
curl -X PATCH https://api.gitmoap.example/access/grants/{grant_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Revoke access, or give up your own

Revoking a grant takes effect on the next request; the history of what they did stays exactly where it is. Anyone can give up a grant they hold themselves.

Warning

Revoking access is immediate. Anything that depended on it — a clone, a pipeline token, an open review — fails from the next request on. Giving the access back restores what they can do, not anything that failed in between.

Open Settings, then Members, and use the bin icon on the person's row for a person's direct grant. Your own row offers Leave instead. Other grants are revoked through the CLI and the API.

Terminalbash
gitmoap access delete-access-grant --grant-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/access/grants/{grant_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Ask the three questions from the terminal

Three commands take a path instead of an id, and answer the questions people ask most:

  • gitmoap access why acme/platform/api --user alice --permission code.push — whether Alice can push, and every grant, limit and boundary behind the answer. --user takes a username, an email address or an id; leave it out to ask about yourself.
  • gitmoap access who acme/platform/api — who can reach it, their role, where each grant was made and when it ends, up to a thousand entries. Narrow it with --search.
  • gitmoap access grant acme/platform/api alice bob team:<team-id> --role developer --expires 90d --reason "Billing rewrite" — give a role to people by username or email, to teams, or to service accounts (sa:<id>). --role takes a built-in role or role:<role-id> for one your organisation defined; --expires takes a number of days or a date such as 2027-01-31, which ends at the close of that day in UTC. Add --dry-run to see who would gain what first.

A path names a repository if there is one at it, and a group otherwise. Add --output json to any of the three for the full answer instead of the summary.

Know when access ends

Access with an end date stops on its own: the next request after the date is refused, with nothing to remember to undo. A week before the end, the person holding it and whoever gave it each get an item in their attention inbox, once. A month after the end, the lapsed grant is removed from the list; until then an explanation shows it as expired.

Advanced: cap what someone can do, whatever they are given

A limit sets the most a person, a team, every service account, every AI agent, or everyone can do on a group or repository and everything beneath it — Contractors at most Reporter on billing, service accounts at most Developer anywhere, everyone read-only on the archive. Grants still say what people can do; a limit takes away whatever goes above it. A limit reaches everything beneath where it is set, even a repository that has stopped inheriting access, so switching that off can never undo it. The organisation's Owners are never limited.

A limit that allows nothing on a Public or Internal group or repository removes every grant, but the person can still read what the visibility lets anyone read — the answer says so with a warning.

Only an Owner sets and removes limits, and a token needs access:administer, which no bundle includes. Add dry_run=true to see who a limit would narrow before setting it.

On the list of who has access, a grant that a limit narrows carries that limit in limited_to — whether the limit names the person, a team they are on, or everyone — so the cap shows on the row it caps.

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access create-group-access-limit \
--group-id <id> \
--ceiling '<json>' \
--subject '<json>'
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/access/limits \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "ceiling": "<object>", "subject": "<object>" }'

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access create-repo-access-limit \
--repo-id <id> \
--ceiling '<json>' \
--subject '<json>'
curlbash
curl -X POST https://api.gitmoap.example/repositories/{repo_id}/access/limits \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "ceiling": "<object>", "subject": "<object>" }'

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access list-group-access-limits --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/access/limits \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access list-repo-access-limits --repo-id <id>
curlbash
curl -X GET https://api.gitmoap.example/repositories/{repo_id}/access/limits \
-H "Authorization: Bearer $GITMOAP_TOKEN"

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access delete-access-limit --limit-id <id>
curlbash
curl -X DELETE https://api.gitmoap.example/access/limits/{limit_id} \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Advanced: only people added here

Switch off inheritance on a group or repository, and grants made on the groups above stop reaching it: only the people given access there, or beneath it, keep theirs. The incident repository for the security team is the classic case — everyone is Developer at the top, and still nobody but the security team can open it. Limits still reach it, and the organisation's Owners are never shut out.

Only an Owner switches this, with access:administer. A top-level group has nothing above it, so it cannot be switched. Switching it off is refused when it would take away your own right to switch it back; ask an Owner of the organisation. Add dry_run=true to see who would lose access first.

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access update-group-access-inheritance \
--group-id <id> \
--inherits-access
curlbash
curl -X PUT https://api.gitmoap.example/groups/{group_id}/access/inheritance \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "inherits_access": "<boolean>" }'

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access update-repo-access-inheritance \
--repo-id <id> \
--inherits-access
curlbash
curl -X PUT https://api.gitmoap.example/repositories/{repo_id}/access/inheritance \
-H "Authorization: Bearer $GITMOAP_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "inherits_access": "<boolean>" }'

See everyone in your organisation

Everyone who holds access anywhere in your organisation — people and service accounts — longest-standing first: whether they are a member — access at the top-level group, or a place on one of its teams — or an outside collaborator, whose access is all further down; how many grants name them directly; their teams; their role at the top, when a grant there names them; when they were first given access; and when they last used any of it. Filter by kind with kind=member or kind=outside_collaborator, or search by username. Access that has ended does not count.

Ask it of your organisation's top-level group; a subgroup is refused with NOT_AN_ORGANIZATION.

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access list-org-people --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/people \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Review your organisation's access

The review is the list an Owner works through every quarter: every grant with a reason to look at it, oldest first, each with its reasons.

ReasonThe grant…
expiringends within 30 days
sensitivecarries a role that can change who has access, delete things, or read secrets
unusedis older than 90 days and has not let anyone do anything in that time
redundantadds nothing: other access the same person or team holds — their own grants and their teams' — already gives everything it does there, and lasts at least as long. covered_by lists those grants, with their role and where each was made
outside_collaboratoris held by someone who is not a member of the organisation
service_accountis held by a service account

A grant is used when it lets someone do something — read a page, push a commit, approve a merge. Gitmoap notes the day, once a day, so last_exercised_at on a grant says when that last happened. Access reached only through a public or internal group's visibility is not a use of any grant.

A grant can have several reasons, and access that has already ended is not listed. Only the organisation's Owners can read the review: it lists access on groups and repositories that others may not be able to see. Ask for one reason at a time with reason. A page can hold fewer grants than you asked for — even none — while more remain, because each request reads a bounded stretch of the organisation's grants: keep reading while next_token comes back.

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access list-org-access-review --group-id <id>
curlbash
curl -X GET https://api.gitmoap.example/groups/{group_id}/access/review \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Revoke several grants at once

Name up to 100 of your organisation's grants, by id, on its top-level group — you need Reporter there; naming one twice counts once. Each is checked as if you were revoking it for someone else on its own — you need Maintainer where it was made, and to hold its role yourself — and they all go together or none do: if one would leave the organisation without an Owner, is a service account's own level, or is one you could not revoke, nothing is revoked. A grant you cannot see, or one from another organisation, is refused as not found, for the whole request. To give up your own access, revoke that grant on its own.

Warning

Revoking access is immediate, for every grant in the request at once. Anything that depended on one of them — a clone, a pipeline token, an open review — fails from the next request on. Each revocation is recorded in your audit log with the role it took away.

The web app has no screen for this yet; use the CLI or the API.

Terminalbash
gitmoap access revoke-access-grants --group-id <id> --grant-ids '<json>'
curlbash
curl -X POST https://api.gitmoap.example/groups/{group_id}/access/grants/revoke \
-H "Authorization: Bearer $GITMOAP_TOKEN"

Look up what a role can do

Every permission a role can hold, how sensitive it is, whether visibility alone allows it, and the lowest built-in role that has it; plus the five built-in roles, each with everything it holds. Anyone can read it, signed in or not.

The web app has no screen for this; use the CLI or the API.

Terminalbash
gitmoap access list-access-permissions
curlbash
curl -X GET https://api.gitmoap.example/access/permissions

Reference

19 endpoints

GET/groups/{group_id}/accessaccess:read

Who has access to a group, and why.

list_group_accessGET /groups/{group_id}/accessOpen in the API reference

Token scope

access:read

Send

Path
  • group_iduuidGroup ID
Query
  • sourcealldirectinheritedoptionalOnly grants made here (direct), only inherited ones (inherited), or both (all, the default)
  • subject_kindservice_accountteamuseroptionalOnly grants to people, service accounts or teams
  • searchstringoptionalA fragment of a username, or of a team's name
  • limitint64optionalMaximum number of entries, 1-100 (default 50)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Invalid filter or pagination token
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Group not found

Receive

  • advanced_in_usebooleanWhether the organization has given a team access, or defined a custom role or a limit. Until it has, access is five roles given to people, and the page reads as a members list.
  • entries[]object12 fields
    • created_atdate-time
    • expires_atdate-timeoptional
    • grant_iduuid
    • granted_byobjectoptional
    • last_exercised_atdate-timeoptionalWhen the grant last let somebody do something, to the day. Absent until it has.
    • reasonobjectoptional
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • sourceobject1 field
      • kinddirectinheritedWhere an entry on the access page comes from.
    • subjectobjectWho holds a grant.
  • next_tokenobjectoptional
  • organization_ownersobject2 fields
    • countint64
    • listedbooleanWhether their grants appear among the entries. They do not when this group or repository stops inheriting access, yet the owners reach it all the same.
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
GET/repositories/{repo_id}/accessaccess:read

Who has access to a repository, and why.

list_repo_accessGET /repositories/{repo_id}/accessOpen in the API reference

Token scope

access:read

Send

Path
  • repo_iduuidRepository ID
Query
  • sourcealldirectinheritedoptionalOnly grants made here (direct), only inherited ones (inherited), or both (all, the default)
  • subject_kindservice_accountteamuseroptionalOnly grants to people, service accounts or teams
  • searchstringoptionalA fragment of a username, or of a team's name
  • limitint64optionalMaximum number of entries, 1-100 (default 50)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Invalid filter or pagination token
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Repository not found

Receive

The same shape list_group_access returns — 5 fields. Go to it

Show it here
  • advanced_in_usebooleanWhether the organization has given a team access, or defined a custom role or a limit. Until it has, access is five roles given to people, and the page reads as a members list.
  • entries[]object12 fields
    • created_atdate-time
    • expires_atdate-timeoptional
    • grant_iduuid
    • granted_byobjectoptional
    • last_exercised_atdate-timeoptionalWhen the grant last let somebody do something, to the day. Absent until it has.
    • reasonobjectoptional
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • sourceobject1 field
      • kinddirectinheritedWhere an entry on the access page comes from.
    • subjectobjectWho holds a grant.
  • next_tokenobjectoptional
  • organization_ownersobject2 fields
    • countint64
    • listedbooleanWhether their grants appear among the entries. They do not when this group or repository stops inheriting access, yet the owners reach it all the same.
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
GET/groups/{group_id}/access/explainaccess:read

Why someone has the access they have on a group.

explain_group_accessGET /groups/{group_id}/access/explainOpen in the API reference

Token scope

access:read

Send

Path
  • group_iduuidGroup ID
Query
  • principal_iduuidoptionalThe person or service account to explain; you, when omitted
  • permissionstringoptionalOne permission key to decide, such as code.push

Refuses

  • 400Unknown permission
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Group or principal not found

Receive

  • boundaryobjectoptional
  • effectiveobject2 fields
    • basisgrantsnoneorganization_ownerWhere an explained principal's access comes from.
    • rankobjectoptional
  • grants[]object10 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • expires_atdate-timeoptional
    • outcomeexpiredreachesstopped_by_boundaryWhat became of one grant in an explanation.
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • viaobjectHow a grant reaches the explained principal.
  • limits[]object5 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • subjectobjectWhom a limit caps.
  • organization_ownerboolean
  • principalobject4 fields
    • avatar_urlobjectoptional
    • iduuid
    • kindagentservice_accountuserA person, a service account or an AI organization's agent.
    • usernamestring
  • scopeobject1 field
    • full_pathstring
GET/repositories/{repo_id}/access/explainaccess:read

Why someone has the access they have on a repository.

explain_repo_accessGET /repositories/{repo_id}/access/explainOpen in the API reference

Token scope

access:read

Send

Path
  • repo_iduuidRepository ID
Query
  • principal_iduuidoptionalThe person or service account to explain; you, when omitted
  • permissionstringoptionalOne permission key to decide, such as code.push

Refuses

  • 400Unknown permission
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Repository or principal not found

Receive

The same shape explain_group_access returns — 7 fields. Go to it

Show it here
  • boundaryobjectoptional
  • effectiveobject2 fields
    • basisgrantsnoneorganization_ownerWhere an explained principal's access comes from.
    • rankobjectoptional
  • grants[]object10 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • expires_atdate-timeoptional
    • outcomeexpiredreachesstopped_by_boundaryWhat became of one grant in an explanation.
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • viaobjectHow a grant reaches the explained principal.
  • limits[]object5 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • subjectobjectWhom a limit caps.
  • organization_ownerboolean
  • principalobject4 fields
    • avatar_urlobjectoptional
    • iduuid
    • kindagentservice_accountuserA person, a service account or an AI organization's agent.
    • usernamestring
  • scopeobject1 field
    • full_pathstring
POST/groups/{group_id}/access/grantsaccess:manage

Give one role to people, service accounts or teams on a group.

create_group_access_grantsPOST /groups/{group_id}/access/grantsOpen in the API reference

Token scope

access:manage

Counts against

members_per_scope

Send

Path
  • group_iduuidGroup ID
Query
  • dry_runbooleanoptionalSay who would gain what, and change nothing
Body
  • expires_atdate-timeoptionalWhen the access ends. Omit it for access that lasts until it is revoked.
  • reasonobjectoptional
  • roleobjectA role, as a grant names it: { "builtin": "developer" } or { "custom": "<role id>" }.

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403The caller cannot give this role here
  • 404Group, role or subject not found
  • 409A subject already holds a different role here
  • 422A service account cannot hold this role here
  • 429Access limit reached for this group

Receive

  • grants[]object15 fields
    • created_atdate-time
    • expires_atdate-timeoptional
    • grant_iduuid
    • granted_byobjectoptional
    • last_exercised_atdate-timeoptionalWhen the grant last let somebody do something, to the day. Absent until it has.
    • reasonobjectoptional
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • scopeobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • subjectobjectWho holds a grant.
    • updated_atdate-time
  • previewobjectoptional
  • unchanged[]object3 fields
    • grant_iduuid
    • reasonone of already_granted_same_roleWhy a subject named in a create request was left as it was.
    • subjectobjectWho a grant is to: { "principal": "<id>" } for a person or a service account, or { "team": "<id>" } for a team of the organization.
POST/repositories/{repo_id}/access/grantsaccess:manage

Give one role to people, service accounts or teams on a repository.

create_repo_access_grantsPOST /repositories/{repo_id}/access/grantsOpen in the API reference

Token scope

access:manage

Counts against

members_per_scope

Send

Path
  • repo_iduuidRepository ID
Query
  • dry_runbooleanoptionalSay who would gain what, and change nothing
Body
  • expires_atdate-timeoptionalWhen the access ends. Omit it for access that lasts until it is revoked.
  • reasonobjectoptional
  • roleobjectA role, as a grant names it: { "builtin": "developer" } or { "custom": "<role id>" }.

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403The caller cannot give this role here
  • 404Repository, role or subject not found
  • 409A subject already holds a different role here
  • 422A service account cannot hold this role here
  • 429Access limit reached for this repository

Receive

The same shape create_group_access_grants returns — 3 fields. Go to it

Show it here
  • grants[]object15 fields
    • created_atdate-time
    • expires_atdate-timeoptional
    • grant_iduuid
    • granted_byobjectoptional
    • last_exercised_atdate-timeoptionalWhen the grant last let somebody do something, to the day. Absent until it has.
    • reasonobjectoptional
    • roleobject4 fields
      • builtinobjectoptional
      • customobjectoptional
      • namestringThe role's display name: a built-in name such as Developer, or the custom role's name.
      • rankdeveloperguestmaintainerownerreporter
    • scopeobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • subjectobjectWho holds a grant.
    • updated_atdate-time
  • previewobjectoptional
  • unchanged[]object3 fields
    • grant_iduuid
    • reasonone of already_granted_same_roleWhy a subject named in a create request was left as it was.
    • subjectobjectWho a grant is to: { "principal": "<id>" } for a person or a service account, or { "team": "<id>" } for a team of the organization.
PATCH/access/grants/{grant_id}access:manage

Change a grant's role, expiry or reason.

update_access_grantPATCH /access/grants/{grant_id}Open in the API reference

Token scope

access:manage

Send

Path
  • grant_iduuidGrant ID
Query
  • dry_runbooleanoptionalSay what would change, and change nothing
Body
  • expires_atdate-timeoptionalA new end date, or null for access that lasts until it is revoked.
  • reasonobjectoptional
  • roleobjectoptional

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403The caller cannot change this grant
  • 404Grant or role not found
  • 409This is a service account's own level — change it on the service account
  • 422A service account cannot hold this role here

Receive

  • grantobjectoptional
  • previewobjectoptional
DELETE/access/grants/{grant_id}access:manage

Revoke a grant, or give up your own.

delete_access_grantDELETE /access/grants/{grant_id}Open in the API reference

Token scope

access:manage

Send

Path
  • grant_iduuidGrant ID
Query
  • dry_runbooleanoptionalSay who would lose what, and change nothing

Refuses

  • 400Bad request — a path parameter is not a well-formed value of its type
  • 401Authentication required
  • 403The caller cannot revoke this grant
  • 404Grant not found
  • 409The organization would be left without an Owner, or this is a service account's own level

Receive

  • changes[]object9 fields
    • principalobject4 fields
      • avatar_urlobjectoptional
      • iduuid
      • kindagentservice_accountuserA person, a service account or an AI organization's agent.
      • usernamestring
    • rank_afterobjectoptional
    • rank_beforeobjectoptional
    • scopeobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
  • summaryobject3 fields
    • people_gainingint32
    • people_losingint32
    • scopes_affectedint64The group or repository the change is made on, plus every one beneath it that inherits access and so changes with it.
  • truncatedbooleanWhether changes stops short of everyone affected. The summary still counts them all.
GET/access/permissionspublic

Every permission a role can hold, and the five built-in roles made of them.

list_access_permissionsGET /access/permissionsOpen in the API reference

Token scope

None — this one is public

Send

Nothing — the address is the whole request.

Receive

  • permissions[]object5 fields
    • applies_atgrouprepositoryrootscopeWhere a grant carrying a permission has any effect.
    • keystringThe permission's key, {area}.{verb} — for example code.push.
    • lowest_builtin_roledeveloperguestmaintainerownerreporter
    • public_readablebooleanWhether a Public or Internal group or repository lets people read this without being given access.
    • tierreservedsensitivestandardHow dangerous granting a permission is, which decides whether a custom role may carry it.
  • roles[]object2 fields
    • namestringThe role's display name, such as Developer.
    • roledeveloperguestmaintainerownerreporter
GET/groups/{group_id}/access/limitsaccess:read

The limits in force on a group: set there, or on a group above it.

list_group_access_limitsGET /groups/{group_id}/access/limitsOpen in the API reference

Token scope

access:read

Send

Path
  • group_iduuidGroup ID
Query
  • limitint64optionalMaximum number of limits, 1-100 (default 50)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Invalid pagination token
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Group not found

Receive

  • limits[]object9 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • created_atdate-time
    • created_byobjectoptional
    • limit_iduuid
    • reasonobjectoptional
    • subjectobjectWhom a limit caps, as a response shows it.
  • next_tokenobjectoptional
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
GET/repositories/{repo_id}/access/limitsaccess:read

The limits in force on a repository: set there, or on a group above it.

list_repo_access_limitsGET /repositories/{repo_id}/access/limitsOpen in the API reference

Token scope

access:read

Send

Path
  • repo_iduuidRepository ID
Query
  • limitint64optionalMaximum number of limits, 1-100 (default 50)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Invalid pagination token
  • 401Authentication required
  • 403The caller cannot see who has access here
  • 404Repository not found

Receive

The same shape list_group_access_limits returns — 3 fields. Go to it

Show it here
  • limits[]object9 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • created_atdate-time
    • created_byobjectoptional
    • limit_iduuid
    • reasonobjectoptional
    • subjectobjectWhom a limit caps, as a response shows it.
  • next_tokenobjectoptional
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
POST/groups/{group_id}/access/limitsaccess:administer

Cap someone on a group and everything beneath it, whatever they are granted.

create_group_access_limitPOST /groups/{group_id}/access/limitsOpen in the API reference

Token scope

access:administer

Counts against

limits_per_scope

Send

Path
  • group_iduuidGroup ID
Query
  • dry_runbooleanoptionalSay who would be narrowed, and change nothing
Body
  • ceilingobjectThe most a limit lets its subject hold: { "builtin": "reporter" }, { "custom": "<id>" }, or { "none": {} } for nothing at all.
  • reasonobjectoptional
  • subjectobjectWhom a limit caps: { "principal": "<id>" }, { "team": "<id>" } or { "class": "everyone" | "service_accounts" | "agents" }.

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403Only the owners of the group can limit access here
  • 404Group, role or subject not found
  • 409The subject is already limited here
  • 429Limit cap reached for this group

Receive

  • limitobject9 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • created_atdate-time
    • created_byobjectoptional
    • limit_iduuid
    • reasonobjectoptional
    • subjectobjectWhom a limit caps, as a response shows it.
  • previewobjectoptional
POST/repositories/{repo_id}/access/limitsaccess:administer

Cap someone on a repository, whatever they are granted.

create_repo_access_limitPOST /repositories/{repo_id}/access/limitsOpen in the API reference

Token scope

access:administer

Counts against

limits_per_scope

Send

Path
  • repo_iduuidRepository ID
Query
  • dry_runbooleanoptionalSay who would be narrowed, and change nothing
Body
  • ceilingobjectThe most a limit lets its subject hold: { "builtin": "reporter" }, { "custom": "<id>" }, or { "none": {} } for nothing at all.
  • reasonobjectoptional
  • subjectobjectWhom a limit caps: { "principal": "<id>" }, { "team": "<id>" } or { "class": "everyone" | "service_accounts" | "agents" }.

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403Only the owners of the repository can limit access here
  • 404Repository, role or subject not found
  • 409The subject is already limited here
  • 429Limit cap reached for this repository

Receive

The same shape create_group_access_limit returns — 2 fields. Go to it

Show it here
  • limitobject9 fields
    • atobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
    • ceilingobjectoptional
    • created_atdate-time
    • created_byobjectoptional
    • limit_iduuid
    • reasonobjectoptional
    • subjectobjectWhom a limit caps, as a response shows it.
  • previewobjectoptional
DELETE/access/limits/{limit_id}access:administer

Remove a limit.

delete_access_limitDELETE /access/limits/{limit_id}Open in the API reference

Token scope

access:administer

Send

Path
  • limit_iduuidLimit ID
Query
  • dry_runbooleanoptionalSay who would stop being narrowed, and change nothing

Refuses

  • 400Invalid limit id or dry_run value
  • 401Authentication required
  • 403Only the owners of the scope can remove its limits
  • 404Limit not found
  • 409The request conflicts with the resource's current state; `reason` says how

Receive

The same shape delete_access_grant returns — 3 fields. Go to it

Show it here
  • changes[]object9 fields
    • principalobject4 fields
      • avatar_urlobjectoptional
      • iduuid
      • kindagentservice_accountuserA person, a service account or an AI organization's agent.
      • usernamestring
    • rank_afterobjectoptional
    • rank_beforeobjectoptional
    • scopeobject3 fields
      • full_pathstring
      • idstring
      • typegrouprepository
  • summaryobject3 fields
    • people_gainingint32
    • people_losingint32
    • scopes_affectedint64The group or repository the change is made on, plus every one beneath it that inherits access and so changes with it.
  • truncatedbooleanWhether changes stops short of everyone affected. The summary still counts them all.
PUT/groups/{group_id}/access/inheritanceaccess:administer

Stop grants made on the groups above from reaching a group, or let them reach it again.

update_group_access_inheritancePUT /groups/{group_id}/access/inheritanceOpen in the API reference

Token scope

access:administer

Send

Path
  • group_iduuidGroup ID
Query
  • dry_runbooleanoptionalSay who would gain or lose access, and change nothing
Body
  • inherits_accessboolean

Refuses

  • 400Invalid request, or a top-level group
  • 401Authentication required
  • 403Only the owners of the group can switch this
  • 404Group not found
  • 409Switching it off would take away your own right to switch it back

Receive

  • dry_runboolean
  • previewobjectoptional
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
PUT/repositories/{repo_id}/access/inheritanceaccess:administer

Stop grants made on the groups above from reaching a repository, or let them reach it again.

update_repo_access_inheritancePUT /repositories/{repo_id}/access/inheritanceOpen in the API reference

Token scope

access:administer

Send

Path
  • repo_iduuidRepository ID
Query
  • dry_runbooleanoptionalSay who would gain or lose access, and change nothing
Body
  • inherits_accessboolean

Refuses

  • 400Invalid request
  • 401Authentication required
  • 403Only the owners of the repository can switch this
  • 404Repository not found
  • 409Switching it off would take away your own right to switch it back
  • 422The request is well-formed but asks for something the resource's rules do not allow; `reason` says which

Receive

The same shape update_group_access_inheritance returns — 3 fields. Go to it

Show it here
  • dry_runboolean
  • previewobjectoptional
  • scopeobject4 fields
    • full_pathstring
    • idstring
    • inherits_accessbooleanWhether access granted on the groups above reaches it. When it is false, only access granted here — and the organization's owners — reach it.
    • typegrouprepository
GET/groups/{group_id}/peopleaccess:read

Everyone with access in an organization, and how each belongs to it.

list_org_peopleGET /groups/{group_id}/peopleOpen in the API reference

Token scope

access:read

Send

Path
  • group_iduuidThe organization's top-level group
Query
  • kindmemberoutside_collaboratoroptionalOnly members (member), or only people whose access is all below the top-level group (outside_collaborator)
  • searchstringoptionalA fragment of a username
  • limitint64optionalMaximum number of people, 1-100 (default 50)
  • next_tokenstringoptionalPagination token

Refuses

  • 400Not an organization's top-level group, or an invalid filter or pagination token
  • 401Authentication required
  • 403The caller cannot see who has access in the organization
  • 404Group not found

Receive

  • next_tokenobjectoptional
  • people[]object9 fields
    • grant_countint64How many grants name them directly, anywhere in the organization.
    • joined_atdate-timeWhen they were first given access here.
    • kindmemberoutside_collaboratorHow someone belongs to an organization.
    • last_exercised_atdate-timeoptionalWhen any of their access here last let them do something, to the day.
    • principalobject4 fields
      • avatar_urlobjectoptional
      • iduuid
      • kindagentservice_accountuserA person, a service account or an AI organization's agent.
      • usernamestring
    • root_roleobjectoptional
GET/groups/{group_id}/access/reviewaccess:read

The organization's access review: every grant with a reason to look at it.

list_org_access_reviewGET /groups/{group_id}/access/reviewOpen in the API reference

Token scope

access:read

Send

Path
  • group_iduuidThe organization's top-level group
Query
  • reasonexpiringoutside_collaboratorredundantsensitiveservice_accountunusedoptionalOnly grants in the review for this reason
  • limitint64optionalMaximum number of grants, 1-100 (default 50)
  • next_tokenstringoptionalPagination token. A page can be shorter than the limit while more remain: keep reading while one is returned

Refuses

  • 400Not an organization's top-level group, or an invalid filter or pagination token
  • 401Authentication required
  • 403The caller is not an owner of the organization
  • 404Group not found

Receive

  • items[]object8 fields
    • grantobject8 fields
      • created_atdate-time
      • expires_atdate-timeoptional
      • grant_iduuid
      • granted_byobjectoptional
      • last_exercised_atdate-timeoptionalWhen the grant last let somebody do something, to the day. Absent until it has.
      • reasonobjectoptional
      • subjectobjectWho holds a grant.
      • updated_atdate-time
  • next_tokenobjectoptional
POST/groups/{group_id}/access/grants/revokeaccess:manage

Revoke several of an organization's grants at once — all of them, or none.

revoke_access_grantsPOST /groups/{group_id}/access/grants/revokeOpen in the API reference

Token scope

access:manage

Send

Path
  • group_iduuidThe organization's top-level group

Refuses

  • 400Not an organization's top-level group, or no grants, or more than 100
  • 401Authentication required
  • 403The caller cannot revoke one of the grants
  • 404A grant is not one of this organization's, or the caller cannot see it
  • 409The organization would be left without an Owner, or a grant is a service account's own level

Receive

No body.

Limits

Grants per group or repository — 50 on the Free plan. People, service accounts and teams given access on one group or repository all count, each once, whatever role they hold. A service account's own level on its home does not count here; it counts against the service-account limit instead.

PlanGrants per group or repository
Free50
Pro200
Team1 000
Enterprise5 000

At the limit, giving more access is refused with 429 TOO_MANY_REQUESTS and nothing is written. Revoke grants that are no longer needed, give a team access instead of each of its members, or raise the limit: a higher plan lifts every group's and repository's allowance, and an Owner can ask for a higher limit on one namespace.

One request names at most 50 subjects, and an end date can be at most a year away.

A bulk revocation names at most 100 grants. Split a longer list into several requests; each is all or nothing on its own.

The access review reads at most 1 000 grants per request. In a large organisation a page can come back short, or empty, with a next_token; keep reading until none comes back.

Limits per group or repository — 10 on the Free plan; 50 on Pro, 100 on Team, 200 on Enterprise. One subject is limited at most once on one group or repository.

Troubleshooting

403 FORBIDDEN— PERMISSION_REQUIRED
Cause
You can see the group or repository but your role here does not let you do this. The message names the permission, as in PERMISSION_REQUIRED: merge_requests.merge is needed here
What to do
Ask why with an explanation of your access for that permission, then ask someone who manages access here
403 FORBIDDEN— ACCESS_ESCALATION
Cause
The role you are giving or taking away holds something you do not, or outranks you
What to do
Ask someone who holds that role to make the change. The message names what you lack
404 NOT_FOUNDon a grant id
Cause
The grant was revoked, or you cannot see who has access where it was made
What to do
Check the id against the list for that group or repository
404 NOT_FOUNDon a subject
Cause
The person, service account or team does not exist in this organisation
What to do
Use an id from this organisation. Another organisation's service account or team cannot be given access here
409 CONFLICT— GRANT_EXISTS
Cause
The subject already has a different role here
What to do
Change their existing grant instead of creating a new one
409 CONFLICT— LAST_ORGANIZATION_OWNER
Cause
Revoking this would leave the organisation without an Owner
What to do
Make someone else an Owner first
409 CONFLICT— USE_SERVICE_ACCOUNT_API
Cause
This grant is a service account's own level
What to do
Change the level on the service account
422 UNPROCESSABLE_ENTITY— SERVICE_ACCOUNT_OUTSIDE_HOME
Cause
The service account belongs to a different group or repository
What to do
Give the access where the account belongs, or beneath it, or use an account that belongs here
422 UNPROCESSABLE_ENTITY— SERVICE_ACCOUNT_CANNOT_BE_OWNER
Cause
A service account was offered Owner
What to do
Maintainer is the most a service account can hold
400 BAD_REQUESTon expires_at
Cause
The date is in the past, or more than a year away
What to do
Pick a date within the next year
409 CONFLICT— LIMIT_EXISTS
Cause
The subject is already limited here
What to do
Remove that limit first, then set the new one
409 CONFLICT— WOULD_LOCK_OUT_ACTOR
Cause
Switching inheritance off would take away your own right to switch it back
What to do
Ask an Owner of the organisation
400 BAD_REQUESTswitching inheritance on a top-level group
Cause
A top-level group has nothing above it
What to do
Switch it on a subgroup or a repository
400 BAD_REQUEST— NOT_AN_ORGANIZATION
Cause
The people page, the review and bulk revocation were asked of a subgroup
What to do
Use the organisation's top-level group
403 FORBIDDENreading the review
Cause
You are not an Owner of the organisation
What to do
Ask an Owner
404 NOT_FOUNDrevoking in bulk
Cause
One of the grants is not this organisation's, or you cannot see it
What to do
Remove it from the list; nothing was revoked
400 BAD_REQUESTrevoking in bulk
Cause
The list was empty, or named more than 100 grants
What to do
Name between 1 and 100, and split a longer list into several requests
403 FORBIDDENon the people page
Cause
You are not Reporter or above on the top-level group
What to do
Ask an Owner of the organisation
The review returns a short or empty page with a next_token
Cause
Each request reads a bounded stretch of the organisation's grants
What to do
Keep reading with the token until none comes back

Examples

Give eight people one product

Everyone on a small team gets Developer on the organisation, in one request, and every repository in it — private ones included — is theirs to push to.

shellscript
gitmoap access create-group-access-grants --group-id acme \
--subjects '[{"principal":"<id-1>"},{"principal":"<id-2>"},{"principal":"<id-3>"}]' \
--role '{"builtin":"developer"}'

Give a contractor access that ends on its own

shellscript
gitmoap access create-repo-access-grants --repo-id acme/billing \
--subjects '[{"principal":"<user-id>"}]' \
--role '{"builtin":"developer"}' \
--expires-at 2026-12-31T00:00:00Z \
--reason "Contractor on the billing rewrite"

Check whether someone can push, and why

shellscript
gitmoap access explain-repo-access --repo-id acme/billing \
--principal-id <user-id> --permission code.push --output json \
| jq '{granted: .verdict.granted, rank: .effective.rank, via: [.grants[] | {at: .at.full_path, outcome}]}'

Review everyone given access directly to a repository

shellscript
gitmoap access list-repo-access --repo-id acme/billing --source direct --limit 100 --output json \
| jq -r '.entries[] | [.role.name, (.subject.principal.username // .subject.team.slug), (.expires_at // "no end"), (.reason // "")] | @tsv'

Clear out access nobody uses

shellscript
gitmoap access list-org-access-review --group-id acme --reason unused --limit 100 --output json \
| jq '{grant_ids: [.items[].grant.grant_id]}' > unused.json
gitmoap access revoke-access-grants --group-id acme --data @unused.json

FAQ

Is the members list the same thing?

It is the simple view of the same grants: the people given a built-in role directly on that group or repository. Anything you do on the members screens shows up here, and the reverse, for people.

Can I see who has access to something I cannot see?

No. If you cannot see a group or repository, you cannot see who has access to it, and a grant made there looks the same as one that does not exist.

Does a public repository make its members public?

No. Visibility decides who can read what the repository publishes. Who has access is not part of that.

How does Gitmoap know a grant is unused?

Each time a grant lets someone do something, the day is written down on it, at most once a day. The review calls a grant unused when that has not happened for 90 days and the grant is older than that.

Why can I not make a service account an Owner?

Owner is the role that decides who else has access and whether a group or repository survives. That stays with people.

  • Groupshow groups nest, which is how access flows down
  • Repositorieswhat each role lets someone do in a repository
  • Teamsnamed sets of people, given access as one
  • Custom rolesyour organisation's own roles, for when the five built-in ones do not fit
  • Service accountsaccounts for automation, and changing their own level
  • Access tokensthe scopes a token needs to call these operations
  • Audit logevery change to access, with the role before and after