Custom roles
Overview
The five built-in roles — Guest, Reporter, Developer, Maintainer and Owner — cover most of what people do. When they do not, your organisation can define its own: a custom role starts from a built-in role and adds or removes individual permissions. Developer, but without deleting branches. Reporter, plus the audit log. Someone from support who reads and files issues and never sees the code.
You define a role once, for the whole organisation, and give it the way you give a built-in role — to a person, a service account or a team, on any group or repository. What the role allows applies there and everywhere beneath it.
Quick start
Define a role that reads and files issues without reading code, and give it to someone
Define it:
gitmoap roles create-role --group-id acme --slug stakeholder --name Stakeholder --base-role guest --added '["issues.read","issues.create"]'.Give it:
gitmoap access create-repo-access-grants --repo-id acme/billing --subjects '[{"principal":"<user-id>"}]' --role '{"custom":"<role-id>"}'.They can now open the repository, read its issues and file new ones — and not browse its code.
Before you start
- Owner of the organisation to define, change or delete a role.Changing a role changes what everyone who holds it can do, everywhere it is given, so it stays with the people who own the organisation.
- Anyone in the organisation can see its rolesand where they are used. Someone whose only access is to one repository sees none of this.
- Giving a custom role is giving access.It needs
access.managewhere you give it, and you can only give a role whose every permission you hold yourself — see Access. - A token with the right scope, if you are using the CLI or the API:
role:readto list and read, which comes withread_api;role:manageto define, change and delete, which no bundle includes — ask for it by name.
How it works
A role is a base, plus additions, less removals. The base is Guest, Reporter, Developer or Maintainer. Add permissions to it or remove them from it, by key — code.push, issues.create, audit.read. The full list, with what each permission needs, is in the permission catalogue: gitmoap access list-access-permissions.
An addition brings what it needs. Add code.push and the role holds code.read too, because pushing code you cannot read makes no sense. A removal never takes anything else with it: if you remove a permission that something the role keeps needs, the role is refused and the message names the pair, so you decide what to do rather than finding out later.
Some permissions are never in a custom role. Deleting a group or repository, changing its visibility, and deciding who else administers access belong to Owner alone. A role cannot be based on Owner either.
A role counts as its base wherever a level is compared. A branch rule that lets Developers and above push lets in a role based on Developer — but only if the role still holds code.push. Removing a permission always wins.
Changing a role changes everyone who holds it. No grant moves, and yet every person, service account and team given the role gains or loses what you changed, everywhere it reaches. Preview the change first: the answer lists the permissions gained and lost, and how many grants and limits use the role. Every change lands in the audit log with the same.
A role in use cannot be deleted. Change or revoke its grants first — removing someone's access should be something you do to them on purpose, not a side effect of tidying the list.
How to
Define a role
Choose a slug — lowercase letters, digits and hyphens, unique in your organisation — a display name, a base, and the permissions to add and remove.
The web app has no screen for this yet; use the CLI or the API.
gitmoap roles create-role \ --group-id <id> \ --base-role guest|reporter|developer|maintainer|owner \ --name <name> \ --slug <slug>curl -X POST https://api.gitmoap.example/groups/{group_id}/roles \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "base_role": "<one of developer | guest | maintainer | owner | reporter>", "name": "<string>", "slug": "<string>" }'See your organisation's roles
The web app has no screen for this yet; use the CLI or the API.
gitmoap roles list-roles --group-id <id>curl -X GET https://api.gitmoap.example/groups/{group_id}/roles \ -H "Authorization: Bearer $GITMOAP_TOKEN"The web app has no screen for this yet; use the CLI or the API.
gitmoap roles get-role --role-id <id>curl -X GET https://api.gitmoap.example/roles/{role_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Change a role, after seeing what it changes
Send only what changes. added and removed, when you send them, replace the lists they name. Add dry_run=true — --dry-run in the CLI — to see the permissions gained and lost and how many grants use the role, and change nothing.
The web app has no screen for this yet; use the CLI or the API.
gitmoap roles update-role --role-id <id>curl -X PATCH https://api.gitmoap.example/roles/{role_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"See where a role is used
Every grant of the role on a group or repository you can see, and how many limits cap someone at it. If it is also given somewhere you cannot see, hidden_scopes says so.
The web app has no screen for this yet; use the CLI or the API.
gitmoap roles get-role-usage --role-id <id>curl -X GET https://api.gitmoap.example/roles/{role_id}/usage \ -H "Authorization: Bearer $GITMOAP_TOKEN"Delete a role
A role still given somewhere, or capping a limit, cannot be deleted: change or revoke those first, using the role's usage to find them.
Warning
Deleting a role cannot be undone. Defining one again with the same name makes a new role, which no grant or limit names until you give it again.
The web app has no screen for this yet; use the CLI or the API.
gitmoap roles delete-role --role-id <id>curl -X DELETE https://api.gitmoap.example/roles/{role_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Reference
6 endpoints
GET/groups/{group_id}/rolesrole:read
An organization's custom roles.
Token scope
role:read
Send
Path
- group_iduuidThe organization's top-level group
Query
- limitint64optionalMaximum number of roles, 1-100 (default 50)
- next_tokenstringoptionalPagination token
Refuses
400Invalid pagination token, or the group is not an organization's top-level group401Authentication required403Only members of the organization see its roles404Group not found
Receive
- next_tokenobjectoptional
roles[]object9 fields
- base_roledeveloperguestmaintainerownerreporter
- created_atdate-time
- created_byobjectoptional
- descriptionstring
- namestring
- organization_iduuid
- role_iduuid
- slugstring
- updated_atdate-time
POST/groups/{group_id}/rolesrole:manage
Define a custom role.
Token scope
role:manage
Counts against
custom_roles_per_org
Send
Path
- group_iduuidThe organization's top-level group
Body
- base_roledeveloperguestmaintainerownerreporter
- descriptionobjectoptional
- namestring
- slugstring
Refuses
400Invalid request, an unknown permission, or the group is not an organization's top-level group401Authentication required403Only the organization's owners define roles404Group not found409The organization already has a role with this slug422The role would be based on Owner, hold a permission only Owner may, or keep a permission without what it needs429Custom role limit reached for this organization
Receive
- base_roledeveloperguestmaintainerownerreporter
- created_atdate-time
- created_byobjectoptional
- descriptionstring
- namestring
- organization_iduuid
- role_iduuid
- slugstring
- updated_atdate-time
GET/roles/{role_id}role:read
One custom role.
Token scope
role:read
Send
Path
- role_iduuidRole ID
Refuses
400Invalid role id401Authentication required403The token lacks the scope404Role not found
Receive
The same shape create_role returns — 9 fields. Go to it
Show it here
- base_roledeveloperguestmaintainerownerreporter
- created_atdate-time
- created_byobjectoptional
- descriptionstring
- namestring
- organization_iduuid
- role_iduuid
- slugstring
- updated_atdate-time
PATCH/roles/{role_id}role:manage
Change a custom role — and with it, everyone who holds it.
Token scope
role:manage
Send
Path
- role_iduuidRole ID
Query
- dry_runbooleanoptionalSay what would change and for how many grants, and change nothing
Body
- base_roleobjectoptional
- descriptionobjectoptional
- nameobjectoptional
- slugobjectoptional
Refuses
400Invalid request, or an unknown permission401Authentication required403Only the organization's owners change roles404Role not found409The organization already has a role with this slug422The role would be based on Owner, hold a permission only Owner may, or keep a permission without what it needs
Receive
- dry_runboolean
- grants_affectedint64Grants of this role, each of which changes with it — on every group and repository they reach.
- limits_affectedint64Limits capping someone at this role.
roleobject9 fields
- base_roledeveloperguestmaintainerownerreporter
- created_atdate-time
- created_byobjectoptional
- descriptionstring
- namestring
- organization_iduuid
- role_iduuid
- slugstring
- updated_atdate-time
DELETE/roles/{role_id}role:manage
Delete a custom role nothing uses.
Token scope
role:manage
Send
Path
- role_iduuidRole ID
Refuses
400Invalid role id401Authentication required403Only the organization's owners delete roles404Role not found409The role is still granted, or is a limit's ceiling
Receive
No body.
GET/roles/{role_id}/usagerole:read
Where a custom role is granted, as far as you can see.
Token scope
role:read
Send
Path
- role_iduuidRole ID
Query
- limitint64optionalMaximum number of grants, 1-100 (default 50)
- next_tokenstringoptionalPagination token
Refuses
400Invalid pagination token401Authentication required403The token lacks the scope404Role not found
Receive
grants[]object7 fields
atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- created_atdate-time
- expires_atdate-timeoptional
- grant_iduuid
- subjectobjectWho holds a grant.
- hidden_scopesbooleanWhether the role is also granted somewhere you cannot see. Those places are never named.
- limitsint64Limits capping someone at this role, across the organization.
- next_tokenobjectoptional
Limits
Custom roles per organisation — 5 on the Free plan.
| Plan | Custom roles per organisation |
|---|---|
| Free | 5 |
| Pro | 20 |
| Team | 50 |
| Enterprise | 50 |
At the limit, defining another is refused with 429 TOO_MANY_REQUESTS. Delete roles nobody uses, or raise the limit: a higher plan lifts it, and an Owner can ask for a higher limit on one namespace.
Troubleshooting
403 FORBIDDENdefining or changing a role
- Cause
- Only the organisation's Owners manage roles, or your token lacks
role:manage - What to do
- Ask an Owner, or mint a token that names
role:manage
400 BAD_REQUESTnaming a permission
- Cause
- The key is not in the catalogue
- What to do
- Check the spelling against
gitmoap access list-access-permissions
422 UNPROCESSABLE_ENTITY— RESERVED_PERMISSION
- Cause
- The role would hold something only Owner may
- What to do
- Leave it out; give Owner to the people who need it
422 UNPROCESSABLE_ENTITY— PERMISSION_DEPENDENCY
- Cause
- A removal leaves a permission without what it needs, or removes
scope.view - What to do
- Remove the dependent permission too, or keep what it needs
422 UNPROCESSABLE_ENTITY— ROLE_BASE_OWNER
- Cause
- Owner was chosen as the base
- What to do
- Base the role on Maintainer or below
409 CONFLICT— ROLE_SLUG_TAKEN
- Cause
- Your organisation already has a role with this slug
- What to do
- Choose another slug
409 CONFLICT— ROLE_IN_USE
- Cause
- The role is still given somewhere, caps a limit, or is what single sign-on gives newcomers — the connection's default role or a pending invite
- What to do
- Find the grants with the role's usage, change or revoke them, change the sign-on default and the invites, then delete
409 CONFLICT— USE_ACCESS_API changing someone on the members page
- Cause
- Their access here is a custom role
- What to do
- Change or revoke it through access
Examples
A release manager who cannot delete branches
gitmoap roles create-role --group-id acme --slug release-manager --name "Release manager" \ --base-role developer --added '["policies.manage"]' --removed '["code.delete_branches"]'Check what removing a permission would do before doing it
gitmoap roles update-role --role-id <role-id> --removed '["merge_requests.merge"]' --dry-run \ --output json | jq '{lost: .permissions_removed, grants: .grants_affected}'FAQ
Can a subgroup define its own roles?
No. Roles belong to the organisation, so one name means one thing everywhere in it.
What happens to my roles when the platform adds a permission?
A role gains a new permission if its base role gains it — the same as everyone holding that built-in role. It never gains a permission you removed.
Does a custom role appear on the members page?
No. The members page shows the built-in roles people hold directly; custom roles are listed, changed and revoked through Access.
Related
- Accessgiving a role on a group or repository, and seeing why someone can do something
- Teamsgiving a role to a named set of people at once
- Access tokensthe scopes a token needs to call these operations
- Audit logevery change to a role, with what it added and removed