Reviews and approvals
Overview
A review is how a merge request gets a second pair of eyes before it lands. You put people on a merge request as reviewers, each of them approves it or asks for changes, and the repository's own policy decides how many approvals it takes before the merge button opens. Every one of those steps is recorded on the merge request's timeline, so a month later you can still see who signed off, who asked for changes, and which commits arrived between one decision and the next.
Reviewing is deliberately cheap to undo. An approval can be withdrawn, a rejection can be turned into an approval, and neither one moves any code — the decision is a signal attached to the merge request, and the merge itself is a separate action with its own permission.
Before you start
- A merge request that is open.Every write on this page — assigning, removing, approving, rejecting, withdrawing — is refused once the merge request is merged or closed. Reviews are frozen at that point on purpose: the record of who approved what stays exactly as it was when the work landed.
- Developer access to the repository to review.Approving, requesting changes and withdrawing a decision all need Developer or above, on every repository whatever its visibility, and you have to be one of the merge request's reviewers as well. Reading is looser: a public repository lets anyone read the reviewer list and the timeline, and an internal one any signed-in account — but reading a merge request is not the same as being trusted to approve it.
- Developer access to assign or remove a reviewer.That bar is deliberately higher than reviewing: adding somebody puts an item in their inbox, and removing somebody deletes their recorded decision. There is no separate path for the author — an author with Developer access manages their own reviewer list under exactly the same check as anyone else.
- The person you are assigning already has a Gitmoap account and can read the merge request.You identify them by their account id alone; we look up their name and email ourselves. Somebody who cannot read the merge request is refused with
422, and an id that names nobody with404. A reviewer is a person or a service account — there is no invite-by-email path here — and a service account's approval counts only toward a rule that accepts service accounts. - A token with the right scope, if you are calling the API or the CLI rather than clicking:
reviewer:readto list reviewers,reviewer:manageto assign or remove one,reviewer:reviewto approve, reject or withdraw, andmerge_request:readto read the timeline. Scopes are fixed when a token is minted.reviewer:reviewis in no bundle — not evenwrite_api— so a token can approve only if you name that scope when you mint it.
How it works
One decision per reviewer, and it can always be changed
Every reviewer on a merge request holds exactly one of three states. There is no per-file or per-comment approval: when you approve, you approve the merge request as it stands.
| Your state | Approve | Request changes | Withdraw |
|---|---|---|---|
pending — assigned, no decision | becomes approved | becomes rejected | refused with 400 — there is nothing to withdraw |
approved | no change, still approved | becomes rejected | back to pending |
rejected | becomes approved | no change, still rejected | back to pending |
Withdrawing exists for the case the other two do not cover: you asked for changes, the author has since convinced you it was fine, and you want to clear the block without pretending to have reviewed the current revision. It puts you back to pending, which is the honest state — assigned, no verdict.
Which approvals count
How many approvals a merge request needs, and from whom, is decided by the approval rules that apply to it — defined on the repository, or on a group above it — not by the review. With no rule, reviews are recorded and the merge is not gated on them. With rules, each approval is checked when somebody tries to merge: whether the approver is among a rule's approvers, whether they are a person or a service account and approved while signed in or with a token, and whether the repository's approval policy excludes them for having written or pushed the change. An approval that does not count stays on the merge request, and the merge check says why. See Approval rules.
Approving is never refused for being the author or a committer: the approval is recorded, and the approval policy decides whether it counts. That is the difference between may approve — Developer on the repository — and counts.
Code-owner approval is a further gate and it does not live with the others: it comes from the rule on the target branch. Where it is on, every owner of every changed path has to be among the approvers — an individual owner has to approve in person, a group owner is satisfied when anyone with Developer or above on that group approves — granted on the group or on a group above it; being a member of the repository is not enough, and a group from another organization owns nothing here — a team owner, written @organisation:team in a CODEOWNERS file, is satisfied when anyone on the team approves, and ownership comes from a CODEOWNERS file or, for a path no rule in that file claims, from the service catalog. An owner's approval counts here on the same terms as an approval rule counting people: from a person, not excluded by the approval policy, and — where approvals reset on push — given at the current head. See Repository rules.
A rejection is a harder block than a missing approval: where the repository blocks on rejection, the merge stays shut until that reviewer approves or withdraws, and pushing more commits does not clear it. This is the deliberate asymmetry — an approval is a statement about a specific revision, so a new revision can supersede it, while a rejection is a statement about the change and only the person who made it can retract it.
An approval remembers which revision it was cast against
When you approve, we record the commit at the tip of the source branch at that moment. If commits land afterwards, your approval is reported as stale on every reviewer listing — a plain flag saying "this person approved an older revision".
Staleness is information, always. Whether it blocks is the repository's decision: with approvals set to reset on push, a stale approval stops counting and the merge is refused until the reviewer looks again; without it, the merge proceeds and the stale marker is there for anyone reading the reviewer list. Pending and rejected reviewers are never stale, because neither state is a claim about a revision.
The timeline is written just behind the action
Every reviewer change and every review decision is appended to the merge request's activity timeline, alongside the events the merge request generates itself. We write those entries just after the action completes rather than inside it, so an entry can take a moment to appear — a fraction of a second under normal conditions. A call that came back successfully did the thing; if the timeline has not caught up, refresh.
Entries are append-only. Nothing edits or deletes one, and the only thing that removes them is deleting the merge request, which takes its whole timeline with it.
| Entry | What it records |
|---|---|
created, updated, closed, reopened, merged | the merge request's own lifecycle; updated carries the fields that changed, with their before and after values |
reviewer_added, reviewer_removed | who was put on or taken off the review, and who did it |
review_approved, review_rejected, review_unapproved | a reviewer's decision, and its withdrawal |
commit_pushed | commits arriving on the source branch, each with its hash and subject |
thread_resolved, thread_unresolved | a discussion thread being closed or reopened |
retargeted | the merge request being pointed at a different target branch, with the branch it left, the one it moved to, and whether the platform did it for you |
restacked | the merge request being rebuilt on top of a moved base, with the base and head it had before and after |
Assigning somebody is what reaches them
A review request is not an email you send separately. Adding a reviewer creates a review requested item in that person's attention inbox, and their decision clears it: approving or rejecting discharges their own request, rejecting puts a changes requested item in front of the author, and an approval that brings the count up to the repository's threshold puts a ready to merge item in front of the author instead. Removing a reviewer discharges their request without recording a decision.
Withdrawing does not put the request back. It returns you to pending and it rechecks whether the merge request is still ready to merge, but the inbox item your decision cleared stays cleared — so if you want to be chased again, ask to be re-added rather than assuming the inbox will remind you.
How to
Ask somebody to review
Assign the people you want on the merge request. Assigning is idempotent — adding somebody who is already a reviewer succeeds and changes nothing, including their existing decision — so a script that reconciles a reviewer list can run repeatedly without special-casing.
Open the merge request and find its Reviewers panel. Search for the person you want and pick them, and they join the list as Pending. Each reviewer's row shows their decision as it lands, and an approved row picks up a stale marker once newer commits arrive on the source branch.
gitmoap reviewers add \ --repo-id <id> \ --mr-id <id> \ --reviewer-id <reviewer id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/reviewers \ -H "Authorization: Bearer $GITMOAP_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "reviewer_id": "<uuid>" }'One thing to know if you are scripting this: a repeat add answers with the reviewer reported as pending, because the response describes the row the call asked for rather than the one already stored. The stored decision is untouched — read the reviewer list if you need the true state.
Assigning does not require the person to be a repository member with any particular level — but somebody who cannot see the repository cannot see the merge request either, so assign people who have at least Reporter access, or make sure the repository is internal or public.
See who is reviewing, and where each of them stands
The reviewer list is the answer to "why is this still open". It carries each reviewer's decision, when they made it, and whether an approval has since been superseded by new commits.
The Reviewers panel on the merge request shows every reviewer with their current state — Pending, Approved or Changes requested — and the time of their last decision. An approval cast before the newest commits is marked stale. The merge panel shows the same information as a count against the repository's threshold.
gitmoap reviewers list --repo-id <id> --mr-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/reviewers \ -H "Authorization: Bearer $GITMOAP_TOKEN"The list is ordered by when each person was assigned, so the person who has been waiting longest is first.
Approve a merge request
Approving records your sign-off against the current tip of the source branch. If you had asked for changes, approving replaces that rejection. If you had already approved, approving again re-stamps the decision against the tip as it is now — which is exactly how you clear a stale marker once you have read the commits that arrived since.
Open the merge request, read the diff, and choose Approve in the review controls beside it. Your row in the Reviewers panel flips to Approved, and the merge panel recounts against the repository's threshold.
gitmoap reviewers approve-mr-review --repo-id <id> --mr-id <id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/approve \ -H "Authorization: Bearer $GITMOAP_TOKEN"Two repository settings can refuse your approval outright, both with a 400: one prevents the author of a merge request from approving their own work, and one prevents anybody named as the committer of one of its commits from approving it. Neither is a bug on your side — they are policy, and the repository's merge request settings say which are on.
Ask for changes
Rejecting is the "not like this" signal. It records the same way an approval does, and where the repository blocks on rejection it holds the merge shut until you change your mind.
Open the merge request and choose Request changes in the review controls. Leave your reasoning as a comment first — the decision itself carries no message, and a rejection with no explanation is the single most annoying thing a reviewer can do. Your row in the Reviewers panel turns to Changes requested and the author gets an inbox item.
gitmoap reviewers reject-mr-review --repo-id <id> --mr-id <id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/reject \ -H "Authorization: Bearer $GITMOAP_TOKEN"A rejection does not expire. New commits on the source branch make approvals stale, but they leave rejections exactly where they are, so come back and approve or withdraw once the author has answered you.
Withdraw your review
Withdrawing resets you to pending from either an approval or a rejection. Use it to release a block you no longer stand behind, or to take back an approval you cast too quickly.
Open the merge request and choose Withdraw review in the review controls. The control appears only when you have a decision to withdraw. Your row returns to Pending and the approval count drops by one if you had approved.
gitmoap reviewers unapprove-mr-review --repo-id <id> --mr-id <id>curl -X POST https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/unapprove \ -H "Authorization: Bearer $GITMOAP_TOKEN"Withdrawing when there is nothing to withdraw is refused with 400 rather than silently accepted, because a withdrawal is an explicit act and a no-op withdrawal almost always means you meant to act on a different merge request. The same 400 answers a caller who is not a reviewer on the merge request at all — withdraw is the one review call that does not separate "you have no decision here" from "you are not on this review", so check the reviewer list before assuming which it was.
Take somebody off the review
Warning
Removing a reviewer deletes their recorded decision along with their assignment. An approval you remove stops counting toward the threshold immediately, and re-adding the same person puts them back as Pending — their approval does not come back, and they have to cast it again. There is no undo, and the only record that they ever approved is the timeline entry.
Removing is idempotent: taking off somebody who is not a reviewer succeeds and changes nothing, so a reconciling script never has to check first.
Open the merge request and use the Reviewers panel. Removing somebody is on their row in the list; check the decision shown there before you do it, because that is what you are discarding. Their review-requested inbox item is cleared at the same time.
gitmoap reviewers remove --repo-id <id> --mr-id <id> --reviewer-id <id>curl -X DELETE https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/reviewers/{reviewer_id} \ -H "Authorization: Bearer $GITMOAP_TOKEN"There is no self-removal shortcut. Taking yourself off a review goes through the same Developer check as taking off anybody else — if you cannot, withdraw your review instead and say why in a comment.
Read the history of a merge request
The timeline answers "what happened here" for a merge request you did not follow live: who was assigned and when, every decision and every reversal, the commits that arrived between them, and the retargets and restacks that moved the thing under everybody's feet.
The Activity tab of a merge request shows the timeline newest first, interleaved with the discussion. Scrolling to the bottom loads the next page.
gitmoap mr-activities list-activities --repo-id <id> --mr-id <id>curl -X GET https://api.gitmoap.example/repositories/{repo_id}/merge-requests/{mr_id}/activities \ -H "Authorization: Bearer $GITMOAP_TOKEN"Pages come back newest first, twenty at a time by default and up to a hundred, with a cursor for the next page. Because the ordering is by time rather than by offset, activity arriving while you page never shifts a row from one page to another or hides one from you.
Reference
7 endpoints
POST/repositories/{repo_id}/merge-requests/{mr_id}/reviewersreviewer:manage
Add a reviewer to a merge request
Token scope
reviewer:manage
Counts against
reviewers_per_mr
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Body
- reviewer_iduuid
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404The merge request or the reviewer does not exist409The request conflicts with the resource's current state; `reason` says how422The reviewer cannot read this merge request429Reviewer limit reached for this merge request
Receive
- approved_at_shaobjectoptional
- auth_methodsessiontokenunknownHow a review was made — recorded when it is made, because nothing can recover it later.
- created_atstring
- merge_request_idint64
- principal_kindagentservice_accountuserA person, a service account or an AI organization's agent.
- repository_iduuid
- review_statusapprovedpendingrejected
- reviewed_atobjectoptional
reviewerobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- stalebooleanWhether this reviewer's approval predates the current source-branch tip
(i.e. was cast against an older revision). Computed at read time; not
stored. Purely informational — whether staleness blocks a merge is
governed by the approval policy's
reset_on_push, surfaced per approval in the merge check. Always present in responses; it is recomputed on every read.
GET/repositories/{repo_id}/merge-requests/{mr_id}/reviewersreviewer:read
List reviewers for a merge request
Token scope
reviewer:read
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Refuses
400Bad request — a path parameter is not a well-formed value of its type401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found — or found and private, which is masked as absent
Receive
[]object13 fields
- approved_at_shaobjectoptional
- auth_methodsessiontokenunknownHow a review was made — recorded when it is made, because nothing can recover it later.
- created_atstring
- merge_request_idint64
- principal_kindagentservice_accountuserA person, a service account or an AI organization's agent.
- repository_iduuid
- review_statusapprovedpendingrejected
- reviewed_atobjectoptional
reviewerobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- stalebooleanWhether this reviewer's approval predates the current source-branch tip
(i.e. was cast against an older revision). Computed at read time; not
stored. Purely informational — whether staleness blocks a merge is
governed by the approval policy's
reset_on_push, surfaced per approval in the merge check. Always present in responses; it is recomputed on every read.
POST/repositories/{repo_id}/merge-requests/{mr_id}/approvereviewer:review
Approve a merge request
approve_mr_reviewPOST /repositories/{repo_id}/merge-requests/{mr_id}/approveOpen in the API referenceToken scope
reviewer:review
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Refuses
400MR not open401Unauthenticated — no credential, or an invalid one403Not a reviewer of this merge request, or below Developer on the repository404Not found — or found and private, which is masked as absent409The request conflicts with the resource's current state; `reason` says how
Receive
The same shape add_reviewer returns — 10 fields. Go to it
Show it here
- approved_at_shaobjectoptional
- auth_methodsessiontokenunknownHow a review was made — recorded when it is made, because nothing can recover it later.
- created_atstring
- merge_request_idint64
- principal_kindagentservice_accountuserA person, a service account or an AI organization's agent.
- repository_iduuid
- review_statusapprovedpendingrejected
- reviewed_atobjectoptional
reviewerobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- stalebooleanWhether this reviewer's approval predates the current source-branch tip
(i.e. was cast against an older revision). Computed at read time; not
stored. Purely informational — whether staleness blocks a merge is
governed by the approval policy's
reset_on_push, surfaced per approval in the merge check. Always present in responses; it is recomputed on every read.
POST/repositories/{repo_id}/merge-requests/{mr_id}/rejectreviewer:review
Reject a merge request
Token scope
reviewer:review
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Refuses
400MR not open401Unauthenticated — no credential, or an invalid one403Not a reviewer of this merge request, or below Developer on the repository404Not found — or found and private, which is masked as absent409The request conflicts with the resource's current state; `reason` says how
Receive
The same shape add_reviewer returns — 10 fields. Go to it
Show it here
- approved_at_shaobjectoptional
- auth_methodsessiontokenunknownHow a review was made — recorded when it is made, because nothing can recover it later.
- created_atstring
- merge_request_idint64
- principal_kindagentservice_accountuserA person, a service account or an AI organization's agent.
- repository_iduuid
- review_statusapprovedpendingrejected
- reviewed_atobjectoptional
reviewerobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- stalebooleanWhether this reviewer's approval predates the current source-branch tip
(i.e. was cast against an older revision). Computed at read time; not
stored. Purely informational — whether staleness blocks a merge is
governed by the approval policy's
reset_on_push, surfaced per approval in the merge check. Always present in responses; it is recomputed on every read.
POST/repositories/{repo_id}/merge-requests/{mr_id}/unapprovereviewer:review
Unapprove a merge request (withdraw approval)
unapprove_mr_reviewPOST /repositories/{repo_id}/merge-requests/{mr_id}/unapproveOpen in the API referenceToken scope
reviewer:review
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Refuses
400MR not open or not approved401Unauthenticated — no credential, or an invalid one403Not a reviewer of this merge request, or below Developer on the repository404Not found — or found and private, which is masked as absent409The request conflicts with the resource's current state; `reason` says how
Receive
The same shape add_reviewer returns — 10 fields. Go to it
Show it here
- approved_at_shaobjectoptional
- auth_methodsessiontokenunknownHow a review was made — recorded when it is made, because nothing can recover it later.
- created_atstring
- merge_request_idint64
- principal_kindagentservice_accountuserA person, a service account or an AI organization's agent.
- repository_iduuid
- review_statusapprovedpendingrejected
- reviewed_atobjectoptional
reviewerobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- stalebooleanWhether this reviewer's approval predates the current source-branch tip
(i.e. was cast against an older revision). Computed at read time; not
stored. Purely informational — whether staleness blocks a merge is
governed by the approval policy's
reset_on_push, surfaced per approval in the merge check. Always present in responses; it is recomputed on every read.
DELETE/repositories/{repo_id}/merge-requests/{mr_id}/reviewers/{reviewer_id}reviewer:manage
Remove a reviewer from a merge request
remove_reviewerDELETE /repositories/{repo_id}/merge-requests/{mr_id}/reviewers/{reviewer_id}Open in the API referenceToken scope
reviewer:manage
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
- reviewer_iduuidReviewer ID
Refuses
400The merge request is not open401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Reviewer not found409The request conflicts with the resource's current state; `reason` says how
Receive
No body.
GET/repositories/{repo_id}/merge-requests/{mr_id}/activitiesmerge_request:read
List activities for a merge request
list_activitiesGET /repositories/{repo_id}/merge-requests/{mr_id}/activitiesOpen in the API referenceToken scope
merge_request:read
Send
Path
- repo_iduuidRepository ID
- mr_idint64Merge request ID
Query
- limitint32optionalPage size (1-100, default 20)
- cursorstringoptionalPagination cursor
Refuses
400Invalid request401Unauthenticated — no credential, or an invalid one403Forbidden — the caller's access level or token scope is insufficient404Not found
Receive
activities[]object10 fields
- activity_iduuid
- activity_typeclosedcommit_pushedcreatedmergedpipeline_finishedreopenedrestackedretargetedreview_approvedreview_rejectedreview_unapprovedreviewer_addedreviewer_removedthread_resolvedthread_unresolvedupdated
actorobject4 fields
- avatar_urlobjectoptional
- emailstring
- idstring
- usernamestring
- created_atstring
- merge_request_idint64
- metadataobject
- repository_iduuid
- next_cursorobjectoptional
Limits
The number of reviewers on a single merge request is capped, and the cap depends on the plan of the namespace the repository lives in.
| Plan | Reviewers per merge request |
|---|---|
| Free | 25 |
| Pro | 50 |
| Team | 100 |
| Enterprise | 200 |
At the cap, the assignment is refused with 429 TOO_MANY_REQUESTS and nothing is created — the existing reviewers are untouched and no half-added row is left behind. Remove a reviewer who has already given their verdict, or raise the limit: an Owner of the root group can request an increase for the namespace, and a raised limit applies immediately to every repository under it, up to a ceiling of 1000 reviewers per merge request.
Nothing else on this page is metered. Approving, rejecting, withdrawing, removing a reviewer and reading the timeline are all uncapped.
Troubleshooting
400 BAD_REQUESTon any call, mentioning a path parameter
- Cause
- The repository id or merge request number in the URL is not a well-formed value — usually a repository path pasted where an id belongs, or a merge request title where its number belongs
- What to do
- Use the repository's id and the merge request's number as shown in its URL
400 BAD_REQUESTon approve, reject or withdraw, mentioning open merge requests
- Cause
- The merge request is merged or closed, and reviews are frozen once it leaves the open state
- What to do
- Reopen the merge request if the review still matters, or open a new one. The recorded decisions stay exactly as they were
400 BAD_REQUESTon assigning or removing a reviewer
- Cause
- Same reason — the reviewer list is frozen on a merge request that is not open
- What to do
- Reopen it first if the assignment is still needed
400 BAD_REQUESTon approve, mentioning the author
- Cause
- The repository prevents the author of a merge request from approving their own work
- What to do
- Ask somebody else to review. If the policy is wrong for this repository, a Maintainer can change it in the merge request settings
400 BAD_REQUESTon approve, mentioning committers
- Cause
- The repository prevents a committer from approving, and your account's email address is the committer address on one of the merge request's commits
- What to do
- Ask a reviewer who committed none of them. Being the committer is not the same as being the author — a commit you only rebased or amended carries your address, even when somebody else wrote it
400 BAD_REQUESTon assigning a reviewer, mentioning a field
- Cause
- The body is not a valid assignment — an account id that is not a UUID, a malformed email address, a missing
usernameoremail, or a field the endpoint does not accept - What to do
- Send exactly the three fields the endpoint takes, with the id and email copied from the person's own account record
400 BAD_REQUESTon withdraw, mentioning nothing to withdraw
- Cause
- You hold no decision to take back: you are
pending, somebody removed and re-added you, which resets it, or you were never a reviewer here at all - What to do
- Open the reviewer list and find your own row. No row means ask to be added; a Pending row means the withdrawal already happened
401 UNAUTHORIZED
- Cause
- No credential was sent, or the one sent has expired
- What to do
- Sign in again, or mint a fresh token. An expired token looks identical to no token at all
403 FORBIDDENon approve or reject
- Cause
- You are not an assigned reviewer on this merge request. Read access is not review authority — the assignment is
- What to do
- Ask the author or any Developer to add you as a reviewer, then cast your decision. Withdraw answers the same case with a
400, not this
403 FORBIDDENon assigning or removing a reviewer
- Cause
- You have read access but not Developer
- What to do
- Ask an Owner or Maintainer of the repository to raise your access level, or ask a Developer to make the change
403 INSUFFICIENT_SCOPE
- Cause
- Your token is valid but was minted without the scope this call needs
- What to do
- Mint a new token carrying
reviewer:manageto assign and remove,reviewer:reviewto approve, reject and withdraw,reviewer:readto list reviewers, ormerge_request:readto read the timeline. Scopes are chosen at mint time and cannot be added afterwards
404 NOT_FOUNDon a repository or merge request you are sure exists
- Cause
- Either the repository is private and you are not a member, or the merge request number is wrong
- What to do
- A private repository answers
404rather than403to a non-member, so that its existence stays private. Check your membership first, then the number
409 CONFLICTon assigning a reviewer
- Cause
- The id is a well-formed UUID but no user account has it — most often a service account id, or an id copied from somewhere that is not this account's user record
- What to do
- Look the person up by username and take the id from that record. Reviewers are user accounts, so no service account id will ever be accepted here
429 TOO_MANY_REQUESTSon assigning a reviewer
- Cause
- The merge request is at its reviewer cap
- What to do
- Remove a reviewer who has already given their verdict, or ask an Owner of the root group to request a limit increase for the namespace
The merge is still blocked although the approval count is met
- Cause
- An outstanding rejection, a missing code-owner approval, or approvals that went stale when new commits landed
- What to do
- Open the merge request's merge panel — it lists every check with its own result, including the name of anyone whose rejection is holding it and the code owners still missing
An approval shows as stale
- Cause
- Commits landed on the source branch after that approval was cast
- What to do
- Ask the reviewer to look again and approve. Whether staleness actually blocks the merge depends on the repository's reset-on-push setting, shown in the merge panel
A reviewer's approval vanished
- Cause
- Somebody removed and re-added them, which discards the decision along with the assignment
- What to do
- Ask them to approve again. The timeline shows who removed them and when
The timeline is missing something that just happened
- Cause
- Timeline entries are written just behind the action rather than inside it
- What to do
- Refresh in a moment. If the action returned successfully, it happened; the entry is on its way
A reviewer never saw the request
- Cause
- They have no access to the repository, so the merge request is invisible to them
- What to do
- Add them to the repository or its group as a Reporter, then re-check their inbox — the review request is already waiting
Removing a reviewer returned success but nothing changed
- Cause
- Removal is idempotent, and the person was not a reviewer on this merge request
- What to do
- Check the id you sent. Removing somebody who is not assigned is treated as a request that got what it asked for
FAQ
Can a service account approve a merge request?
No. A reviewer is a user account, so a service account can neither be assigned nor cast a decision. Use a personal access token belonging to the person whose judgement the approval represents.
Does approving merge anything?
No. Approving records a signal; merging is a separate action with its own permission, and a merge request with every approval it needs still sits there until somebody merges it.
Can I approve without being assigned?
No — the assignment is the authority. Ask to be added; anybody with Developer access on the repository can do it, including the author.
Can I review a draft?
Yes. A draft cannot be merged until it is marked ready, but it takes reviewers and decisions like any other open merge request — which is the point of marking it a draft rather than waiting to open it.
Do approvals survive a rebase or a force-push?
The approval row survives, but it is marked stale, because it was cast against a revision that no longer exists on the branch. Whether it still counts is the repository's reset-on-push setting.
Who can see who approved?
Anyone who can see the merge request. The reviewer list and the timeline follow the repository's visibility exactly — public repositories answer anyone, private ones answer members.
Related
- Merge requestsopening, updating, merging and closing the merge request a review is attached to
- Merge request settingsthe approvals threshold, author and committer restrictions, reset-on-push and blocking on rejection, per repository or inherited from a group
- Merge request discussionthe comments and inline threads a review decision is usually explained in
- Repository rulesthe code-owner requirement that adds a second approval gate on top of the threshold
- Diffs and comparisonsreading the change you are being asked to approve
- Your dashboardthe attention inbox that a review request lands in, and the list of merge requests waiting on you
- Access tokensminting a token with the review scopes a script needs