Document the edition-dependent response of the merge request approvals endpoints
What does this MR do?
GET /projects/:id/merge_requests/:merge_request_iid/approvals, POST .../approve and POST .../unapprove all answer through the present_approval helper. In Community Edition that helper presents API::Entities::MergeRequestApprovals, which has four attributes: user_has_approved, user_can_approve, approved and approved_by. Enterprise Edition overrides it in ee/lib/ee/api/merge_request_approvals.rb (the comment there cites #408183) to present merge_request.approval_state with API::Entities::ApprovalState, which has 24. The override sits in the prepended block with no license check, so every Enterprise Edition instance answers with the 24 attributes, licensed or not, and GitLab.com does too.
The page showed neither shape completely. The example under the GET carried 13 of the 24 attributes, with approved set to true while approvals_left was still 1, the example under approve carried 12, and the unapprove section did not say what it returns.
This MR:
- Says which attributes each edition returns, in the GET section, next to the
approvedparagraph !243030 (merged) added, which I left as it was. - Adds a response attribute table for the GET, sorted as the style guide asks, marking the 20 attributes only Enterprise Edition returns and the three deprecated ones:
approversandapprover_groups(the entity marks both@deprecated, they read only the first approval rule) andrequire_password_to_approve(deprecated in GitLab 16.9, as the project-level section of the same page already says). - Gives a complete example response for each edition. The keys are in the order the entities expose them, which is the order GitLab.com returns, and each user object carries the eight keys GitLab.com returns for a
UserBasic. - Says that approve and unapprove answer
201 Createdwith the same attributes, and makes the approve example the complete Enterprise Edition response.
How I checked what each edition returns
- Code, at master
f1d4fb37:lib/api/merge_request_approvals.rb(present_approval, called by all three routes) andlib/api/entities/merge_request_approvals.rbfor Community Edition;ee/lib/ee/api/merge_request_approvals.rbandee/lib/api/entities/approval_state.rbfor Enterprise Edition.ApprovalStatemergesIssuableEntity(id,iid,project_id,title,description,state,created_at,updated_at) and adds 16 of its own,invalid_approvers_rulesthe last of them.approval_rules_leftandinvalid_approvers_rulesrender withApprovalRuleShort(id,name,rule_type). - GitLab.com, 2026-10-05, anonymous:
GET /projects/278964/merge_requests/259297/approvalsreturns the 24 keys in exactly the order of the new example. A public merge request in a personal namespace without a paid plan returns the same 24 keys, withmerge_request_approvers_availableandmultiple_approval_rules_availablebothfalse. - Self-managed Enterprise Edition without a license: I did not measure it. The override has no license condition, and the response #602776 reports from an instance in that state carries
approvals_requiredandapprovals_left, which only the Enterprise Edition entity exposes. - Example values: I kept the existing ones and made them agree with each other. In the GET example one regular rule named
Ruby(the rule the/approval_stateexample on the same page uses) requires two approvals and has one, soapprovedisfalse,approval_rules_leftlists that rule andsuggested_approverslists the user who has not approved. In the approve example both have approved.
What this MR leaves as it is: the OpenAPI document
The desc blocks of the three routes declare success ::API::Entities::MergeRequestApprovals, so doc/api/openapi/openapi_v3.yaml and the reference generated from it describe four attributes, also for Enterprise Edition, where the response has 24. That schema also types approved_by as one object rather than an array, because the CE entity exposes it without is_array. The override changes only the helper the routes call, so nothing in the annotation sees it.
I would like to fix that too, and there is a precedent for it: EE::API::Entities::MergeRequestBasic prepends approvals_before_merge onto the CE entity, and the OpenAPI document, generated from the EE codebase, lists the key under APIEntitiesMergeRequestBasic. The same shape here would be an EE::API::Entities::MergeRequestApprovals module prepended onto the CE entity with the 20 Enterprise Edition exposures, reading merge_request.approval_state. The present_approval override could then go, with the deprecated POST .../approvals, the one other route it serves, presenting ApprovalState directly as its annotation already says. The other direction is what #408183 proposes, moving the CE code into EE.
That change needs a backend reviewer and a regenerated openapi_v3.yaml, so I kept this MR to the page. I am happy to open it as a follow-up, or to add it here if you prefer one MR. Either way, which of the two directions would you rather have?
Lint
scripts/lint-doc.sh doc/api/merge_request_approvals.md, run in the lint image the script names (markdownlint-cli2 0.23.2, Vale 3.21.0): passed, no markdownlint issues and no Vale errors.lychee --offline --no-progress --include-fragmentson the page, as thedocs-lint linksjob runs it: 0 errors.- Vale at every level compared with master: no new warnings and no new suggestions. The page's reading level moves from 8.90 to 9.05.
- Every JSON block on the page parses, apart from the two that annotate their keys with
//comments on purpose. That includes the example under "List all approval rules for a project", which on master carried two trailing commas after"contains_hidden_groups": false; this MR removes them.
Where this comes from
I maintain gitlab-mcp-server, an MCP server that exposes the GitLab API to AI assistants. Its merge request approval tool took the four attributes from the generated OpenAPI document and dropped the other 20 on every Enterprise Edition instance, GitLab.com included. Reading the code and GitLab.com's answer to fix that is what showed the page and the annotation disagreeing with both. The project keeps a record of everything it finds in its dependencies and in sibling projects in upstream-bugs.md.
Related issues
Related to #408183
Related to #602776
Author's checklist
- Optional. Consider taking the GitLab Technical Writing Fundamentals course.
- Follow the:
- If you're adding a new page, add the product availability details under the H1 topic title.
- If you are a GitLab team member, request a review based on:
- The documentation page's metadata.
- The associated Technical Writer.
If you are a GitLab team member and only adding documentation, do not add any of the following labels:
~"frontend"~"backend"~"type::bug"~"database"
These labels cause the MR to be added to code verification QA issues.
Reviewer's checklist
Documentation-related MRs should be reviewed by a Technical Writer for a non-blocking review, based on Documentation Guidelines and the Style Guide.
If you aren't sure which tech writer to ask, use roulette or ask in the #docs Slack channel.
- If the content requires it, ensure the information is reviewed by a subject matter expert.
- Technical writer review items:
- Ensure docs metadata is present and up-to-date.
- Ensure the appropriate labels are added to this MR.
- Ensure a release milestone is set.
- If relevant to this MR, ensure content topic type principles are in use, including:
- The headings should be something you'd do a Google search for. Instead of
Default behavior, say something likeDefault behavior when you close an issue. - The headings (other than the page title) should be active. Instead of
Configuring GDK, say something likeConfigure GDK. - Any task steps should be written as a numbered list.
- If the content still needs to be edited for topic types, you can create a follow-up issue with the docs-technical-debt label.
- The headings should be something you'd do a Google search for. Instead of
- Review by assigned maintainer, who can always request/require the reviews above. Maintainer's review can occur before or after a technical writer review.