Vetrix Docs

Merge requests

Reference for the merge-request endpoints under /api/v1/repos/{owner}/{repo}/merges: opening, reading, reviewing, updating, and merging merge requests, plus their commits, diffs, review comments, and review decisions.

Resource overview

A merge request proposes integrating a source branch into a target branch in one repository. Every endpoint on this page is scoped to a single repository and hangs off the base path:

/api/v1/repos/{owner}/{repo}/merges

A merge request is addressed by its per-repository number (1-based, assigned at creation), not by its id UUID. The sub-resources documented here are the merge request itself, its commits, its diff (whole and per file), its review comments, and its review decisions.

/merges is the canonical path. The older /pulls paths are deprecated: each answers with an HTTP 308 Permanent Redirect to the equivalent /merges path, preserving the request method. Call /merges directly. The naming rule and the wire-level identifiers that intentionally keep the pull name (the pull_request webhook event and the /ws/repos/{owner}/{repo}/pulls/{number} live-update WebSocket) are described in conventions.md.

Auth & scopes

See conventions.md for the accepted credential types and how scopes are enforced. Merge requests are a repository sub-resource, so OAuth2 and personal-access-token callers use the repository scopes:

Operation OAuth2 scope PAT scope
Read (list, get, commits, files, comments) read:repo repo:read
Write (create, update, merge, comment, review) write:repo repo:write

The operative authorization is a per-operation repository permission, checked in addition to the scope above and listed with each endpoint: a read permission for the read endpoints, an open permission for create, an update/close permission for PATCH, and a merge permission for the merge action. Comment edits and deletes are restricted to the comment's author or a repository administrator.

Reads of a private repository's merge requests follow the enumeration-resistant 404 rule: a caller who may not read the repository receives 404 Not Found, not 403. See errors.md.

Endpoints

Method Path Summary
GET /api/v1/repos/{owner}/{repo}/merges List merge requests (paginated)
GET /api/v1/repos/{owner}/{repo}/merges/{number} Fetch one merge request
POST /api/v1/repos/{owner}/{repo}/merges Open a merge request
PATCH /api/v1/repos/{owner}/{repo}/merges/{number} Update state, draft flag, or description
POST /api/v1/repos/{owner}/{repo}/merges/{number}/merge Merge a merge request
GET /api/v1/repos/{owner}/{repo}/merges/{number}/commits List the commits in a merge request
GET /api/v1/repos/{owner}/{repo}/merges/{number}/files List changed files (paginated summary)
GET /api/v1/repos/{owner}/{repo}/merges/{number}/files/{path} Fetch the structured diff for one file
GET /api/v1/repos/{owner}/{repo}/merges/{number}/comments List review comments
POST /api/v1/repos/{owner}/{repo}/merges/{number}/comments Create a review comment
PATCH /api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid} Edit or resolve a review comment
DELETE /api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid} Delete a review comment
POST /api/v1/repos/{owner}/{repo}/merges/{number}/reviews Submit a review decision
POST /api/v1/repos/{owner}/{repo}/merges/prepare-url Build a prefilled new-merge-request URL

The merge request object

List, get, create, update, and merge all return the same merge request object. List rows are lightweight: they carry the core fields and the author, but the diff, line stats, commit count, reviewers, and labels are returned at their zero values. Fetch a single merge request to get the enriched object.

{
  "id": "<uuid>",
  "repo_id": "<uuid>",
  "number": 42,
  "title": "Add retry budget to the runner poller",
  "description": "Markdown body of the merge request.",
  "state": "open",
  "is_draft": false,
  "source_branch": "feature/retry-budget",
  "target_branch": "main",
  "source_branch_protected": false,
  "author": { "id": "<uuid>", "username": "alice", "display_name": "Alice", "avatar_url": "/user-content/..." },
  "assignees": [],
  "reviewers": [ { "user": { "id": "<uuid>", "username": "bob", "display_name": "Bob", "avatar_url": "..." }, "state": "approved" } ],
  "labels": [ { "id": "<uuid>", "name": "backend", "color": "1f6feb" } ],
  "milestone": null,
  "ci_status": "none",
  "commits_count": 3,
  "additions": 48,
  "deletions": 12,
  "diff": "diff --git a/... b/...",
  "diff_suppressed": false,
  "files": [ ... ],
  "diff_caps": { "max_file_bytes": 1048576, "max_total_bytes": 10485760, "max_file_lines": 5000, "max_files": 500 },
  "merge_commit": "9f1c2e0...",
  "created_at": "2026-06-01T12:00:00Z",
  "updated_at": "2026-06-02T09:30:00Z",
  "merged_at": "2026-06-02T09:30:00Z",
  "closed_at": null
}
Field Type Description
id string Stable UUID. Addressing uses number, not id.
number integer Per-repository merge-request number.
state string open, merged, or closed.
is_draft boolean Draft merge requests cannot be merged until cleared.
source_branch / target_branch string Branches being merged from / into.
source_branch_protected boolean Whether the source branch matches a protected-branch rule.
author object User summary: id, username, display_name, avatar_url.
reviewers array Each entry is { user, state }; state is pending, approved, or changes_requested.
labels array Each entry is { id, name, color }.
ci_status string Pipeline status for the merge request; none when no pipeline applies.
commits_count integer Commits reachable from the source branch but not the target.
additions / deletions integer Line counts across the diff.
diff string Full unified diff. Empty on list rows and whenever the diff is suppressed.
diff_suppressed boolean true when the diff body was deliberately omitted.
diff_suppressed_reason string Present only when diff_suppressed is true. One of git_backend_unavailable, repository_lookup_failed, compare_branches_failed, binary_files_only, diff_too_large, total_size_cap, file_count_cap.
files array Per-file projection of the diff. Omitted when empty; use the files endpoints for large diffs.
diff_caps object The operator-configured diff thresholds that drove suppression. Omitted when not applicable.
merge_commit string Merge commit SHA. Present once merged.
created_at / updated_at string RFC 3339 timestamps.
merged_at / closed_at string RFC 3339 timestamps; present once the merge request reaches that state.

GET /api/v1/repos/{owner}/{repo}/merges

List a repository's merge requests, newest first, with pagination. Requires read access to the repository.

Path parameters

Name Type Description
owner string Repository owner's username.
repo string Repository name.

Query parameters

Name Type Default Description
state string open Filter by state: open, merged, closed, or all.
page integer 1 See Pagination.
per_page integer 25 Clamped to [1, 100]. See Pagination.

Response

{
  "items": [ { "id": "<uuid>", "number": 42, "title": "...", "state": "open", "...": "..." } ],
  "total": 137,
  "page": 1,
  "per_page": 25
}

items are lightweight merge request objects (see above); fetch a single merge request for the diff, stats, reviewers, and labels.

Status codes

Status When
200 OK Listing succeeded.
404 Not Found Repository missing, or private and not readable — see Errors.

Example

curl -H "Authorization: Bearer <token>" \
  "https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges?state=open&per_page=25"

GET /api/v1/repos/{owner}/{repo}/merges/{number}

Fetch one merge request, enriched with its diff, line stats, commit count, and reviewers. Requires read access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Query parameters

Name Type Default Description
algorithm string operator default, else myers Line-diff algorithm: myers or histogram.

Response

A single merge request object (see The merge request object).

Status codes

Status When
200 OK Merge request returned.
400 Bad Request number is not an integer, or algorithm is unknown.
404 Not Found Repository or merge request missing, or private and not readable.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}

POST /api/v1/repos/{owner}/{repo}/merges

Open a merge request. Requires permission to open merge requests in the repository.

Request body

{
  "title": "Add retry budget to the runner poller",
  "description": "Markdown body of the merge request.",
  "source_branch": "feature/retry-budget",
  "target_branch": "main",
  "draft": false
}
Field Type Required Description
title string yes Merge-request title.
source_branch string yes Branch to merge from.
target_branch string yes Branch to merge into.
description string no Markdown body. body is accepted as an alias; when both are sent, description wins.
draft boolean no Open as a draft. Defaults to false.

Response

201 Created with the new merge request object.

Status codes

Status When
201 Created Merge request opened.
400 Bad Request Invalid body, missing title, or missing source_branch/target_branch.
403 Forbidden The caller may not open merge requests here. A non-member of a private repository receives 403, not 404: opening gates on a per-operation permission, so this endpoint does not follow the enumeration-resistant 404 rule.
404 Not Found Repository missing.
409 Conflict A required-pipeline rule blocked the open; the body carries the rule's reason code.

Example

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"title":"Add retry budget","source_branch":"feature/retry-budget","target_branch":"main"}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges

PATCH /api/v1/repos/{owner}/{repo}/merges/{number}

Change a merge request's state, draft flag, or description. Send only the fields you want to change; sending none is an error. Requires update permission (closing requires the close permission).

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Request body

{
  "state": "closed",
  "draft": false,
  "description": "Updated Markdown body."
}
Field Type Description
state string open or closed. Other values are rejected.
draft boolean true sets the draft flag; false clears it. Only valid while the merge request is open.
description string New Markdown body. body is accepted as an alias.

Response

200 OK with the updated merge request object.

Status codes

Status When
200 OK Update applied.
400 Bad Request No fields supplied, an invalid state, or a draft change on a closed/merged merge request.
403 Forbidden The caller may not update or close this merge request. A non-member of a private repository receives 403, not 404: updating gates on a per-operation permission, so this endpoint does not follow the enumeration-resistant 404 rule.
404 Not Found Repository or merge request missing.

Example

curl -X PATCH -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"state":"closed"}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}

POST /api/v1/repos/{owner}/{repo}/merges/{number}/merge

Merge a merge request into its target branch. Requires merge permission. A merge is refused while the merge request is a draft or has any non-approved review, and the synchronous merge may take longer than other API calls on large or contended repositories.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Request body

{
  "strategy": "merge",
  "commit_message": "Merge feature/retry-budget into main",
  "author_name": "Alice",
  "author_email": "alice@example.com",
  "delete_source_branch": true
}
Field Type Description
strategy string merge (explicit merge commit), squash (squash source commits into one), or rebase (rebase source onto target). Defaults to merge when omitted.
commit_message string Message for the merge or squash commit.
author_name / author_email string Commit identity. Default to the caller's username when omitted.
delete_source_branch boolean Delete the source branch after a successful merge. Best-effort; protected branches and the default branch are never deleted, and a deletion failure does not roll back the merge.

Response

200 OK with the merged merge request object (state is merged and merge_commit is populated).

Status codes

Status When
200 OK Merge completed.
400 Bad Request Invalid body or an unknown strategy.
403 Forbidden The caller may not merge this merge request. A non-member of a private repository receives 403, not 404: merging gates on a per-operation permission, so this endpoint does not follow the enumeration-resistant 404 rule.
404 Not Found Repository or merge request missing.
409 Conflict The merge request is a draft (draft_not_ready), has pending reviews (pending_reviews), conflicts with the target (merge conflict), or a required-pipeline rule blocked it (rule reason code).
503 Service Unavailable The merge was canceled before completion; retry.
504 Gateway Timeout The merge timed out; retry.

Example

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"strategy":"squash","commit_message":"Add retry budget","delete_source_branch":true}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/merge

GET /api/v1/repos/{owner}/{repo}/merges/{number}/commits

List the commits a merge request contributes — reachable from the source branch but not the target — newest first. Requires read access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Response

[
  { "sha": "9f1c2e0...", "message": "Add retry budget", "author": "Alice", "date": "2026-06-01T12:00:00Z" }
]

The response is always a JSON array, empty when the merge request contains no commits.

Status codes

Status When
200 OK Commit list returned (possibly empty).
400 Bad Request number is not an integer.
404 Not Found Repository or merge request missing, or private and not readable.
500 Internal Server Error The commits could not be enumerated (for example, a branch ref is gone on an unmerged merge request).

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/commits

GET /api/v1/repos/{owner}/{repo}/merges/{number}/files

List the files a merge request changes, as a paginated summary without hunk bodies. Use this for large diffs where the full diff field on the merge request object is suppressed. Files are ordered alphabetically by new path (falling back to old path for deletions). Requires read access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Query parameters

Name Type Default Description
page integer 1 See Pagination.
per_page integer 50 Clamped to [1, 200].

Response

{
  "files": [
    {
      "old_path": "internal/runner/poll.go",
      "new_path": "internal/runner/poll.go",
      "is_binary": false,
      "is_add": false,
      "is_delete": false,
      "is_rename": false,
      "additions": 18,
      "deletions": 4,
      "suppressed": false
    }
  ],
  "total": 12,
  "page": 1,
  "per_page": 50
}

A file is returned with suppressed: true and a suppressed_reason (for example, a binary file) when its diff is omitted; the rest of the list is still returned.

Status codes

Status When
200 OK File summary returned (files may be empty for a zero-change merge request).
400 Bad Request number is not an integer.
404 Not Found Repository or merge request missing, or private and not readable.
503 Service Unavailable The diff is unavailable (git backend down or comparison failed); the body carries suppressed_reason.

Example

curl -H "Authorization: Bearer <token>" \
  "https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/files?per_page=50"

GET /api/v1/repos/{owner}/{repo}/merges/{number}/files/{path}

Fetch the full structured diff for a single file in a merge request, including hunks and line numbers. The path is matched against the file's new path first, then its old path. Requires read access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.
path string File path within the repository (URL-encoded).

Response

{
  "old_path": "internal/runner/poll.go",
  "new_path": "internal/runner/poll.go",
  "is_binary": false,
  "is_add": false,
  "is_delete": false,
  "is_rename": false,
  "additions": 18,
  "deletions": 4,
  "suppressed": false,
  "hunks": [
    {
      "old_start": 40,
      "old_lines": 6,
      "new_start": 40,
      "new_lines": 8,
      "lines": [
        { "op": " ", "content": "func poll() {", "old_no": 40, "new_no": 40 },
        { "op": "+", "content": "  budget := newBudget()", "old_no": 0, "new_no": 41 }
      ]
    }
  ]
}

In each line, op is " " (context), "+" (added), or "-" (removed); old_no is 0 on added lines and new_no is 0 on removed lines. A binary or oversized file is returned with suppressed: true and empty hunks.

Status codes

Status When
200 OK File diff returned.
400 Bad Request number is not an integer, the path is missing, or the path encoding is invalid.
404 Not Found Repository or merge request missing (or not readable), or no file in the diff matches the path.
503 Service Unavailable The diff is unavailable; the body carries suppressed_reason.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/files/internal%2Frunner%2Fpoll.go

GET /api/v1/repos/{owner}/{repo}/merges/{number}/comments

List the review comments on a merge request. Requires read access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Response

[
  {
    "id": "<uuid>",
    "mr_id": "<uuid>",
    "author_id": "<uuid>",
    "commit_sha": "9f1c2e0...",
    "file_path": "internal/runner/poll.go",
    "line_number": 41,
    "body": "Consider a backoff here.",
    "resolved": false,
    "created_at": "2026-06-01T12:05:00Z",
    "updated_at": "2026-06-01T12:05:00Z"
  }
]

Status codes

Status When
200 OK Comment list returned (possibly empty).
400 Bad Request number is not an integer.
404 Not Found Repository or merge request missing, or private and not readable.

Example

curl -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/comments

POST /api/v1/repos/{owner}/{repo}/merges/{number}/comments

Create a review comment on a merge request. The body is stored as Markdown with HTML entities escaped. Requires write access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Request body

{
  "body": "Consider a backoff here.",
  "commit_sha": "9f1c2e0...",
  "file_path": "internal/runner/poll.go",
  "line_number": 41
}
Field Type Required Description
body string yes Comment text (Markdown).
commit_sha string no Commit the comment anchors to.
file_path string no File the comment anchors to.
line_number integer no Line the comment anchors to.

Response

201 Created with the new comment object.

Status codes

Status When
201 Created Comment created.
400 Bad Request Invalid body or missing body.
404 Not Found Repository or merge request missing, or private and not readable.

Example

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"body":"Consider a backoff here.","file_path":"internal/runner/poll.go","line_number":41}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/comments

PATCH /api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid}

Edit a review comment's body or toggle its resolved flag. Only the comment's author or a repository administrator may do this.

Path parameters

Name Type Description
number integer Per-repository merge-request number.
cid string Comment UUID.

Request body

{
  "body": "Updated comment text.",
  "resolved": true
}

Both fields are optional; send the ones you want to change.

Response

200 OK with the updated comment object.

Status codes

Status When
200 OK Comment updated.
400 Bad Request Invalid body, an invalid number, or an invalid comment id.
403 Forbidden The caller is neither the comment author nor a repository administrator.
404 Not Found Repository, merge request, or comment missing.

Example

curl -X PATCH -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"resolved":true}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid}

DELETE /api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid}

Delete a review comment. Only the comment's author or a repository administrator may do this.

Path parameters

Name Type Description
number integer Per-repository merge-request number.
cid string Comment UUID.

Status codes

Status When
204 No Content Comment deleted.
400 Bad Request Invalid number or comment id.
403 Forbidden The caller is neither the comment author nor a repository administrator.
404 Not Found Repository, merge request, or comment missing.

Example

curl -X DELETE -H "Authorization: Bearer <token>" \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/comments/{cid}

POST /api/v1/repos/{owner}/{repo}/merges/{number}/reviews

Submit a review decision for a merge request. A caller has at most one review; submitting again replaces it. All reviews must be approved before the merge request can be merged. Requires write access to the repository.

Path parameters

Name Type Description
number integer Per-repository merge-request number.

Request body

{
  "state": "approved",
  "body": "Looks good."
}
Field Type Required Description
state string yes approved or changes_requested.
body string no Review summary (Markdown).

Response

{ "id": "<uuid>", "mr_id": "<uuid>", "state": "approved" }

Status codes

Status When
201 Created Review recorded.
400 Bad Request Invalid body, or state is not approved/changes_requested.
404 Not Found Repository or merge request missing, or private and not readable.

Example

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"state":"approved","body":"Looks good."}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/{number}/reviews

POST /api/v1/repos/{owner}/{repo}/merges/prepare-url

Validate a source/target branch pair and return a relative URL to the web new- merge-request form with the fields prefilled. This does not create a merge request. Requires write access to the repository.

Request body

{
  "source_branch": "feature/retry-budget",
  "target_branch": "main",
  "title": "Add retry budget",
  "description": "Markdown body.",
  "draft": false
}
Field Type Required Description
source_branch string yes Branch to merge from; must exist.
target_branch string yes Branch to merge into; must exist.
title string no Prefilled title.
description string no Prefilled Markdown body.
draft boolean no Prefill the draft flag.

Response

{ "url": "/{owner}/{repo}/pulls/new?source_branch=...&target_branch=...", "expires_at": null }

Status codes

Status When
200 OK URL returned.
400 Bad Request Invalid body, or missing source_branch/target_branch.
404 Not Found Repository missing, or private and not readable.
422 Unprocessable Entity The source or target branch does not exist.

Example

curl -X POST -H "Authorization: Bearer <token>" -H "Content-Type: application/json" \
  -d '{"source_branch":"feature/retry-budget","target_branch":"main"}' \
  https://api.gitvetrix.com/api/v1/repos/{owner}/{repo}/merges/prepare-url

Errors

These endpoints use the shared error envelope and status-code conventions in errors.md, including the 404-not-403 rule for private repositories. Beyond the shared catalog, the merge action returns a small set of stable 409 Conflict reason strings in the error field — draft_not_ready, pending_reviews, and merge conflict — plus a required-pipeline rule's own reason code when a merge or open is gated.

Rate limits

Read endpoints are metered as api.read and write endpoints (create, update, merge, comment, review, prepare-url) as api.write, under the standard scope budgets in rate-limits.md. The synchronous merge action runs without the standard write-response deadline so a large merge is not cut off mid-flight; it is still metered as api.write.