Skip to content
GitmoapdocsBeta
Guide· How do I…?

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 with 404. 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:read to list reviewers, reviewer:manage to assign or remove one, reviewer:review to approve, reject or withdraw, and merge_request:read to read the timeline. Scopes are fixed when a token is minted. reviewer:review is in no bundle — not even write_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 stateApproveRequest changesWithdraw
pending — assigned, no decisionbecomes approvedbecomes rejectedrefused with 400 — there is nothing to withdraw
approvedno change, still approvedbecomes rejectedback to pending
rejectedbecomes approvedno change, still rejectedback 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.

EntryWhat it records
created, updated, closed, reopened, mergedthe merge request's own lifecycle; updated carries the fields that changed, with their before and after values
reviewer_added, reviewer_removedwho was put on or taken off the review, and who did it
review_approved, review_rejected, review_unapproveda reviewer's decision, and its withdrawal
commit_pushedcommits arriving on the source branch, each with its hash and subject
thread_resolved, thread_unresolveda discussion thread being closed or reopened
retargetedthe 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
restackedthe 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.

Terminalbash
gitmoap reviewers add \
--repo-id <id> \
--mr-id <id> \
--reviewer-id <reviewer id>
curlbash
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.

Terminalbash
gitmoap reviewers list --repo-id <id> --mr-id <id>
curlbash
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.

Terminalbash
gitmoap reviewers approve-mr-review --repo-id <id> --mr-id <id>
curlbash
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.

Terminalbash
gitmoap reviewers reject-mr-review --repo-id <id> --mr-id <id>
curlbash
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.

Terminalbash
gitmoap reviewers unapprove-mr-review --repo-id <id> --mr-id <id>
curlbash
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.

Terminalbash
gitmoap reviewers remove --repo-id <id> --mr-id <id> --reviewer-id <id>
curlbash
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.

Terminalbash
gitmoap mr-activities list-activities --repo-id <id> --mr-id <id>
curlbash
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

add_reviewerPOST /repositories/{repo_id}/merge-requests/{mr_id}/reviewersOpen in the API reference

Token scope

reviewer:manage

Counts against

reviewers_per_mr

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
Body
  • reviewer_iduuid

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404The merge request or the reviewer does not exist
  • 409The request conflicts with the resource's current state; `reason` says how
  • 422The reviewer cannot read this merge request
  • 429Reviewer 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

list_reviewersGET /repositories/{repo_id}/merge-requests/{mr_id}/reviewersOpen in the API reference

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 type
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not 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 reference

Token scope

reviewer:review

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID

Refuses

  • 400MR not open
  • 401Unauthenticated — no credential, or an invalid one
  • 403Not a reviewer of this merge request, or below Developer on the repository
  • 404Not found — or found and private, which is masked as absent
  • 409The 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

reject_mr_reviewPOST /repositories/{repo_id}/merge-requests/{mr_id}/rejectOpen in the API reference

Token scope

reviewer:review

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID

Refuses

  • 400MR not open
  • 401Unauthenticated — no credential, or an invalid one
  • 403Not a reviewer of this merge request, or below Developer on the repository
  • 404Not found — or found and private, which is masked as absent
  • 409The 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 reference

Token scope

reviewer:review

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID

Refuses

  • 400MR not open or not approved
  • 401Unauthenticated — no credential, or an invalid one
  • 403Not a reviewer of this merge request, or below Developer on the repository
  • 404Not found — or found and private, which is masked as absent
  • 409The 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 reference

Token scope

reviewer:manage

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
  • reviewer_iduuidReviewer ID

Refuses

  • 400The merge request is not open
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Reviewer not found
  • 409The 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 reference

Token scope

merge_request:read

Send

Path
  • repo_iduuidRepository ID
  • mr_idint64Merge request ID
Query
  • limitint32optionalPage size (1-100, default 20)
  • cursorstringoptionalPagination cursor

Refuses

  • 400Invalid request
  • 401Unauthenticated — no credential, or an invalid one
  • 403Forbidden — the caller's access level or token scope is insufficient
  • 404Not 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.

PlanReviewers per merge request
Free25
Pro50
Team100
Enterprise200

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 username or email, 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:manage to assign and remove, reviewer:review to approve, reject and withdraw, reviewer:read to list reviewers, or merge_request:read to 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 404 rather than 403 to 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.

  • 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