Approval rules
Overview
An approval rule says who has to approve a merge request before it can merge, and how many of them. Two people from the Release team for release/*. One code owner of whatever changed. Anyone who may approve, but a person, signed in. A merge request can merge once every rule that applies to its target branch has enough approvals that count.
Two questions are kept apart. May this person approve? is a permission: approving needs Developer or above on the repository. Does their approval count? is decided per rule when the merge is checked: whether the approver is among the rule's approvers, whether they are a person or a service account, whether they approved while signed in or with an access token, and whether they wrote or pushed the change themselves. An approval that does not count is still recorded, and the merge check says why, per approval and per rule.
Rules defined on a group apply to every repository beneath it. A rule can be enforced, so that the groups and repositories beneath may add rules and make it stricter, but never remove or weaken it.
Quick start
Require two approvals from the Release team for every release branch in the organisation
Define the rule on the organisation:
gitmoap approval-rules create-group-approval-rule --group-id acme --name "Release approvers" --applies-to-branches 'release/*' --required 2 --approvers '[{"team":"<team-id>"}]' --enforcement enforced.Open a merge request into
release/2.0in any repository of the organisation, and ask two people from the team to review it.The merge check lists Release approvers with the approvals that count so far; the merge opens at two.
Before you start
- Maintainer on the group or repository to define, change or delete a rule, or to set the approval policy.Anyone who can see the group or repository can read its rules.
- Owner to make a rule or the policy enforced, or to change one that is.An enforced rule is a promise that the groups and repositories beneath do not get a vote, so it stays with the people who administer access.
- Only a group's rules can be enforced.Nothing is beneath a repository to hold to them.
- A token with the right scope, if you are using the CLI or the API:
approval_rule:readto read rules and the policy, which comes withread_api;approval_rule:manageto change them, which comes withwrite_api.
How it works
Which rules apply
The rules that apply to a merge request are every enforced rule on the groups above its repository, then the nearest group's or repository's own rules. A repository that defines its own rules replaces its group's; a group that defines rules replaces those of the groups above it. A rule applies to the branches its pattern names — main, release/* — or to every branch when it names none.
Who a rule accepts
A rule names its approvers with any mix of:
| Approver | Means |
|---|---|
{ "anyone": {} } | anyone who may approve merge requests in the repository |
{ "person": "<id>" } | one person or service account |
{ "team": "<id>" } | whoever is on the team when the merge is checked — someone who has left no longer counts |
{ "role": { "at": { "group": "<id>" }, "min": "maintainer" } } | anyone whose role at that group or repository is at least min, however it was given |
{ "code_owners": {} } | the owners of the files the merge request changes |
It also says which kinds of approval count, in accept_from:
accept_from | Counts an approval from |
|---|---|
people_signed_in (the default) | a person approving in the web app, or in the CLI after gitmoap login — not with an access token |
people | a person, however they signed in, including with a personal access token |
people_and_service_accounts | a person or a service account |
people_and_agents | a person, or an agent of your AI organization — only a reviewer agent can approve |
Who may not approve
The approval policy of a group or repository has three switches that apply to every rule, all off by default:
| Switch | When off, excludes |
|---|---|
author_can_approve | the merge request's author |
committers_can_approve | anyone whose email authored or committed one of its commits |
last_pusher_can_approve | whoever pushed its current head |
and a fourth, reset_on_push: when on, an approval given at an earlier head stops counting once new commits are pushed. The policy of the nearest group or repository that has one applies, tightened by every enforced policy above it.
Everything is checked when the merge is
Nothing about an approver is frozen when they approve except how they were signed in. When the merge is checked — and again when it happens — each approval is asked whether its approver still may approve here, is still among the rule's approvers, and is not excluded by the policy. Someone removed from the team, or whose access has ended, stops counting at once.
Enforced rules can only get stricter
Beneath an enforced rule, a group or repository can strengthen it: define a rule that names it in strengthens, which applies in its place from there down. A strengthening has to be at least as strong in every field, or it is refused with 422 WOULD_WEAKEN_ENFORCED_RULE, naming the field:
| Field | Stronger means |
|---|---|
required | more approvals |
approvers | a subset — fewer people eligible |
accept_from | narrower: people_and_service_accounts or people_and_agents, then people, then people_signed_in — a rule may not count a kind of approval the rule above it does not, so the two wider values do not narrow each other |
applies_to_branches | more branches, or all of them |
An enforced policy works the same way: beneath it, a switch that lets someone approve can only be turned off, and reset_on_push can only be turned on.
How to
See the rules and the policy that apply
The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules get-repo-approval-policy --repo-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/approval-policy \ -H "Authorization: Bearer $GITMOAP_TOKEN"The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules get-group-approval-policy --group-id <id>curl -X GET https://api.gitmoap.example/groups/{group_id}/approval-policy \ -H "Authorization: Bearer $GITMOAP_TOKEN"Define a rule
The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules create-repo-approval-rule \ --repo-id <id> \ --name <name>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/approval-rules \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "<string>" }'The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules create-group-approval-rule \ --group-id <id> \ --name <name>curl -X POST https://api.gitmoap.example/groups/{group_id}/approval-rules \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "<string>" }'Change or delete a rule
Send only what changes. Deleting an enforced rule deletes the rules that strengthen it.
Warning
Deleting a rule takes effect on the next merge check: merge requests it was holding back can merge at once. Deleting an enforced rule also deletes every rule beneath that strengthened it, and defining it again does not bring those back.
The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules update-approval-rule --rule-id <id>curl -X PATCH https://api.gitmoap.example/approval-rules/{rule_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules delete-approval-rule --rule-id <id>curl -X DELETE https://api.gitmoap.example/approval-rules/{rule_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"Set who may not approve
The policy is set whole: send all four switches.
The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules update-repo-approval-policy \ --repo-id <id> \ --author-can-approve \ --committers-can-approve \ --last-pusher-can-approve \ --reset-on-pushcurl -X PUT https://api.gitmoap.example/repositories/{repo_id}/approval-policy \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "author_can_approve": "<boolean>", "committers_can_approve": "<boolean>", "last_pusher_can_approve": "<boolean>", "reset_on_push": "<boolean>" }'The web app has no screen for this yet; use the CLI or the API.
gitmoap approval-rules update-group-approval-policy \ --group-id <id> \ --author-can-approve \ --committers-can-approve \ --last-pusher-can-approve \ --reset-on-pushcurl -X PUT https://api.gitmoap.example/groups/{group_id}/approval-policy \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "author_can_approve": "<boolean>", "committers_can_approve": "<boolean>", "last_pusher_can_approve": "<boolean>", "reset_on_push": "<boolean>" }'Reference
8 endpoints
GET/groups/{group_id}/approval-policyapproval_rule:read
The approval policy and rules in force on a group.
Token scope
approval_rule:read
Send
Path
- group_iduuidGroup ID
Refuses
400Invalid group id401Authentication required403The caller cannot see this group's rules, or the token lacks the scope404Group not found
Receive
effectiveobject4 fields
- author_can_approvebooleanWhether a merge request's author may approve it.
- committers_can_approvebooleanWhether someone who authored or committed a commit in it may approve it.
- last_pusher_can_approvebooleanWhether whoever pushed its current head may approve it.
- reset_on_pushbooleanWhether an approval stops counting when new commits are pushed.
- policyobjectoptional
rules[]object12 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
rules_here[]object7 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
GET/repositories/{repo_id}/approval-policyapproval_rule:read
The approval policy and rules in force on a repository.
Token scope
approval_rule:read
Send
Path
- repo_iduuidRepository ID
Refuses
400Invalid repository id401Authentication required403The caller cannot see this repository's rules, or the token lacks the scope404Repository not found
Receive
The same shape get_group_approval_policy returns — 4 fields. Go to it
Show it here
effectiveobject4 fields
- author_can_approvebooleanWhether a merge request's author may approve it.
- committers_can_approvebooleanWhether someone who authored or committed a commit in it may approve it.
- last_pusher_can_approvebooleanWhether whoever pushed its current head may approve it.
- reset_on_pushbooleanWhether an approval stops counting when new commits are pushed.
- policyobjectoptional
rules[]object12 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
rules_here[]object7 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
PUT/groups/{group_id}/approval-policyapproval_rule:manage
Set a group's approval policy — who may not approve, and whether approvals survive a push.
Token scope
approval_rule:manage
Send
Path
- group_iduuidGroup ID
Body
- author_can_approveboolean
- committers_can_approveboolean
- enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
- last_pusher_can_approveboolean
- reset_on_pushboolean
Refuses
400Invalid request401Authentication required403The caller cannot manage this group's rules, or cannot enforce them404Group not found409The request conflicts with the resource's current state; `reason` says how422A switch would be weaker than a policy enforced above allows
Receive
The same shape get_group_approval_policy returns — 4 fields. Go to it
Show it here
effectiveobject4 fields
- author_can_approvebooleanWhether a merge request's author may approve it.
- committers_can_approvebooleanWhether someone who authored or committed a commit in it may approve it.
- last_pusher_can_approvebooleanWhether whoever pushed its current head may approve it.
- reset_on_pushbooleanWhether an approval stops counting when new commits are pushed.
- policyobjectoptional
rules[]object12 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
rules_here[]object7 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
PUT/repositories/{repo_id}/approval-policyapproval_rule:manage
Set a repository's approval policy — who may not approve, and whether approvals survive a push.
Token scope
approval_rule:manage
Send
Path
- repo_iduuidRepository ID
Body
- author_can_approveboolean
- committers_can_approveboolean
- enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
- last_pusher_can_approveboolean
- reset_on_pushboolean
Refuses
400Invalid request, or enforcement asked of a repository401Authentication required403The caller cannot manage this repository's rules404Repository not found409The request conflicts with the resource's current state; `reason` says how422A switch would be weaker than a policy enforced above allows
Receive
The same shape get_group_approval_policy returns — 4 fields. Go to it
Show it here
effectiveobject4 fields
- author_can_approvebooleanWhether a merge request's author may approve it.
- committers_can_approvebooleanWhether someone who authored or committed a commit in it may approve it.
- last_pusher_can_approvebooleanWhether whoever pushed its current head may approve it.
- reset_on_pushbooleanWhether an approval stops counting when new commits are pushed.
- policyobjectoptional
rules[]object12 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
rules_here[]object7 fields
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
POST/groups/{group_id}/approval-rulesapproval_rule:manage
Define an approval rule on a group, for every repository beneath it.
Token scope
approval_rule:manage
Counts against
approval_rules_per_scope
Send
Path
- group_iduuidGroup ID
Body
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inoptionalWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32optionalDistinct approvals that must count, 1–10.
- strengthensobjectoptional
Refuses
400Invalid request, or `strengthens` names no enforced rule above401Authentication required403The caller cannot manage this group's rules, or cannot enforce them404Group, team or approver scope not found409This group already strengthens that rule422The rule would be weaker than the enforced rule it strengthens429Approval rule limit reached for this group
Receive
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
POST/repositories/{repo_id}/approval-rulesapproval_rule:manage
Define an approval rule on a repository.
Token scope
approval_rule:manage
Counts against
approval_rules_per_scope
Send
Path
- repo_iduuidRepository ID
Body
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inoptionalWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- enforcementdefaultenforcedoptionalWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32optionalDistinct approvals that must count, 1–10.
- strengthensobjectoptional
Refuses
400Invalid request, enforcement asked of a repository, or `strengthens` names no enforced rule above401Authentication required403The caller cannot manage this repository's rules404Repository, team or approver scope not found409This repository already strengthens that rule422The rule would be weaker than the enforced rule it strengthens429Approval rule limit reached for this repository
Receive
The same shape create_group_approval_rule returns — 10 fields. Go to it
Show it here
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
PATCH/approval-rules/{rule_id}approval_rule:manage
Change an approval rule.
Token scope
approval_rule:manage
Send
Path
- rule_iduuidApproval rule ID
Body
- accept_fromobjectoptional
- applies_to_branchesobjectoptional
nullmakes the rule apply to every branch. - enforcementobjectoptional
- nameobjectoptional
- requiredint32optional
Refuses
400Invalid request401Authentication required403The caller cannot manage the rule's group or repository, or cannot enforce it404Approval rule, team or approver scope not found409The request conflicts with the resource's current state; `reason` says how422The rule would be weaker than the enforced rule it strengthens
Receive
The same shape create_group_approval_rule returns — 10 fields. Go to it
Show it here
- accept_frompeoplepeople_and_agentspeople_and_service_accountspeople_signed_inWhich kinds of approval count toward a rule.
- applies_to_branchesobjectoptional
- created_atdate-time
defined_atobject3 fields
- full_pathstring
- idstring
- typegrouprepository
- enforcementdefaultenforcedWhether the scopes beneath may replace a rule, or only strengthen it.
- namestring
- requiredint32Distinct approvals that must count, 1–10.
- rule_iduuid
- strengthensobjectoptional
- updated_atdate-time
DELETE/approval-rules/{rule_id}approval_rule:manage
Delete an approval rule.
Token scope
approval_rule:manage
Send
Path
- rule_iduuidApproval rule ID
Refuses
400Invalid rule id401Authentication required403The caller cannot manage the rule's group or repository, or cannot enforce it404Approval rule not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
Limits
Approval rules per group or repository — 10 on the Free plan. A rule requires between 1 and 10 approvals and names at most 50 approvers.
| Plan | Rules per group or repository |
|---|---|
| Free | 10 |
| Pro | 20 |
| Team | 50 |
| Enterprise | 100 |
At the limit, defining another is refused with 429 TOO_MANY_REQUESTS. An Owner can ask for a higher limit on one namespace.
Troubleshooting
An approval shows counts: false with sign_in_required
- Cause
- It was made with an access token, and the rule counts people signed in
- What to do
- Approve in the web app or after
gitmoap login, or relax the rule topeople
counts: false with service_account_not_accepted
- Cause
- A service account approved, and the rule counts people only
- What to do
- Set
accept_fromtopeople_and_service_accountsif a bot's approval should count
counts: false with agent_not_accepted
- Cause
- An AI agent approved, and the rule does not count agents
- What to do
- Set
accept_fromtopeople_and_agentsif a reviewer agent's approval should count
counts: false with not_an_approver
- Cause
- The approver is not among the rule's approvers — for a team, no longer on it
- What to do
- Ask someone the rule names, or change the rule
counts: false with author, committer or last_pusher
- Cause
- The approval policy excludes whoever wrote or pushed the change
- What to do
- Ask someone else to approve, or change the policy
counts: false with stale
- Cause
- New commits were pushed after the approval, and approvals reset on push
- What to do
- Ask for a fresh approval of the current head
counts: false with cannot_approve
- Cause
- The approver no longer has Developer or above on the repository
- What to do
- Ask someone who does
422— WOULD_WEAKEN_ENFORCED_RULE
- Cause
- A rule or policy beneath an enforced one would be weaker in the field named
- What to do
- Make it at least as strict, or ask an Owner above to change the enforced rule
400enforcing a repository's rule
- Cause
- Only a group's rules can be enforced
- What to do
- Define the rule on the group
403enforcing a rule
- Cause
- Enforcing needs Owner on the group
- What to do
- Ask an Owner
Examples
Count only people, signed in, from the platform team, on main
gitmoap approval-rules create-repo-approval-rule --repo-id acme/billing --name "Platform review" \ --applies-to-branches main --required 1 --approvers '[{"team":"<team-id>"}]'See why an approval does not count
gitmoap merge-requests merge-check --repo-id acme/billing --mr-id 42 --output json \ | jq '.checks[] | select(.check == "approvals") | .rules[] | {name, received, required, approvals}'Stop authors and last pushers approving across the organisation
gitmoap approval-rules update-group-approval-policy --group-id acme --author-can-approve false \ --committers-can-approve true --last-pusher-can-approve false --reset-on-push true --enforcement enforcedFAQ
What happened to "approvals required" in the merge request settings?
It became a rule called Approvals on the same group or repository, counting approvals from anyone, from people — so an approval made with a token still counts, as it did. Its switches became the approval policy.
Can a bot's approval count?
Only where a rule accepts people_and_service_accounts. By default it is recorded and does not count.
Why do approvals from people who left the team stop counting?
Because an approval is a claim that someone eligible agreed to the change now, not that someone once was.
Related
- Reviews and approvalsasking for review, approving and withdrawing
- Merge request settingsthe other gates a merge has to pass
- Repository rulescode-owner approval, and enforced branch and push rules
- Teamsnaming a team as a rule's approvers