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 approved paragraph !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: approvers and approver_groups (the entity marks both @deprecated, they read only the first approval rule) and require_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 Created with 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) and lib/api/entities/merge_request_approvals.rb for Community Edition; ee/lib/ee/api/merge_request_approvals.rb and ee/lib/api/entities/approval_state.rb for Enterprise Edition. ApprovalState merges IssuableEntity (id, iid, project_id, title, description, state, created_at, updated_at) and adds 16 of its own, invalid_approvers_rules the last of them. approval_rules_left and invalid_approvers_rules render with ApprovalRuleShort (id, name, rule_type).
  • GitLab.com, 2026-10-05, anonymous: GET /projects/278964/merge_requests/259297/approvals returns 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, with merge_request_approvers_available and multiple_approval_rules_available both false.
  • 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_required and approvals_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_state example on the same page uses) requires two approvals and has one, so approved is false, approval_rules_left lists that rule and suggested_approvers lists 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-fragments on the page, as the docs-lint links job 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 to #408183

Related to #602776

Author's checklist

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 like Default behavior when you close an issue.
      • The headings (other than the page title) should be active. Instead of Configuring GDK, say something like Configure 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.
  • Review by assigned maintainer, who can always request/require the reviews above. Maintainer's review can occur before or after a technical writer review.
Edited by José M. Requena Plens

Merge request reports

Loading
Loading