Pull requests
Pull requests live per repo. They share the issues row for
title/body/state/timeline; this surface exposes the PR-specific
shape (refs, oids, mergeability, merge state). Reviews and review
comments live in a sibling batch that ships later.
Pull request shape
{
"id": 19,
"number": 1,
"title": "wire up foo",
"body": "first cut",
"state": "open",
"draft": false,
"base_ref": "trunk",
"head_ref": "feature",
"base_oid": "abc…",
"head_oid": "def…",
"mergeable": true,
"mergeable_state": "clean",
"merged": false,
"author_id": 42,
"created_at": "2026-05-12T05:00:00Z",
"updated_at": "2026-05-12T05:00:00Z"
}
mergeable is omitted while the worker hasn't computed it yet
(treat absence as "unknown"). mergeable_state mirrors GitHub's
vocabulary (clean, dirty, blocked, unknown, unstable,
has_hooks, etc. — depending on what the worker recorded).
When merged is true, merge_commit_sha, merge_method, and
merged_at are populated.
List pull requests
GET /api/v1/repos/{owner}/{repo}/pulls
Required scope: repo:read. Paginated; ?per_page= (≤100) and
?page= apply, with the standard Link: header.
Optional ?state=open|closed|all filter (defaults to all);
optional ?draft=true|false filter.
Get a single pull request
GET /api/v1/repos/{owner}/{repo}/pulls/{number}
Required scope: repo:read. 404 when the number refers to an
issue (use the issues surface) or when the caller can't see the
repo.
Create a pull request
POST /api/v1/repos/{owner}/{repo}/pulls
Required scope: repo:write. Policy: ActionPullCreate.
{
"title": "wire up foo",
"body": "first cut",
"base": "trunk",
"head": "feature",
"draft": false
}
| Field | Type | Notes |
|---|---|---|
title |
string | Required, 1–256 chars. |
body |
string | Optional markdown body, ≤65535 chars. |
base |
string | Required, branch name on the target repo. |
head |
string | Required, must differ from base. |
draft |
bool | Open as draft; flip to ready via PATCH. |
Returns 201 with the PR envelope. 422 when head or base
don't resolve, when they're equal, or when no commits are ahead
of base.
Update a pull request
PATCH /api/v1/repos/{owner}/{repo}/pulls/{number}
Required scope: repo:write. Send only the fields you want to
change.
{
"title": "renamed",
"body": "updated body",
"state": "closed",
"draft": false
}
Permission rules:
- Title / body — PR author OR a repo collaborator with write
access. Other callers
403. - State —
ActionPullCloseon the repo. - Draft — flipping
true→false(mark ready) is author-only.false→trueis not supported (422).
List commits in a pull request
GET /api/v1/repos/{owner}/{repo}/pulls/{number}/commits
Required scope: repo:read. Returns the commit set ahead of base
in order, populated by the synchronize worker after open.
List files changed by a pull request
GET /api/v1/repos/{owner}/{repo}/pulls/{number}/files
Required scope: repo:read. Each entry carries path, status
(added/modified/removed/renamed), additions,
deletions, and changes. old_path is set on renames.
Merge a pull request
PUT /api/v1/repos/{owner}/{repo}/pulls/{number}/merge
Required scope: repo:write. Policy: ActionPullMerge.
{
"commit_title": "Optional override",
"commit_message": "Optional body",
"merge_method": "merge",
"sha": "<head_oid>"
}
| Field | Type | Notes |
|---|---|---|
commit_title |
string | Subject override; falls back to "<title> (#<number>)". |
commit_message |
string | Body for the merge commit. Ignored on rebase. |
merge_method |
string | "merge", "squash", "rebase" — must be enabled on the repo. Defaults to the repo's default merge method. |
sha |
string | Optional head_oid guard; 409 on mismatch. |
Returns 200 with the freshly-loaded PR (state now closed,
merged true). 409 when the PR is already merged/closed or
when mergeable_state isn't clean. 422 when the requested
merge method is disabled on the repo or when no commits are
ahead of base. 503 if another merge is in flight.
Reviews
PR reviews bundle a verdict (approve, request_changes, or
comment) with optional body and any draft inline comments the
caller had pending on this PR.
List reviews
GET /api/v1/repos/{owner}/{repo}/pulls/{number}/reviews
Required scope: repo:read. Returns submitted reviews in
creation order.
Submit a review
POST /api/v1/repos/{owner}/{repo}/pulls/{number}/reviews
Required scope: repo:write. Policy: ActionPullReview —
reviewers must be repo collaborators with at least write access.
{ "event": "APPROVE", "body": "looks good to me" }
event is one of APPROVE, REQUEST_CHANGES, COMMENT
(lowercase accepted too). Submitting APPROVE as the PR author
returns 403. Submitting attaches every pending inline comment
the caller authored on this PR.
Inline review comments
List
GET /api/v1/repos/{owner}/{repo}/pulls/{number}/comments
Required scope: repo:read. Returns one entry per inline
comment with file_path, side, original_commit_sha,
original_line, original_position, optional current_position
(absent → outdated), pending, resolved, and threading via
in_reply_to_id.
Add
POST /api/v1/repos/{owner}/{repo}/pulls/{number}/comments
Required scope: repo:write. Policy: ActionPullReview.
{
"body": "nit: unused import",
"file_path": "foo.txt",
"side": "right",
"original_commit_sha": "abc123",
"original_line": 12,
"original_position": 8,
"current_position": 8,
"pending": true
}
pending=true files a draft that's attached to the next review
the caller submits. in_reply_to_id threads under an existing
comment and inherits its diff anchor.
Requested reviewers
List active
GET /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers
Required scope: repo:read. Returns active and historical review
requests with dismissed_at / satisfied_by_review_id set when
applicable.
Request a reviewer
POST /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers
Required scope: repo:write. Policy: ActionPullReview. Body:
{ "user_id": 42 }
or equivalently { "username": "bob" }. 409 when the user is
already an active pending reviewer; 422 on unknown user.
Dismiss a request
DELETE /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers
Required scope: repo:write. Same body. Returns 204; 404
when there is no active request for that user.
Not yet shipped
PUT /pulls/{n}/update-branchPUT /pulls/{n}/auto-merge
View source
| 1 | # Pull requests |
| 2 | |
| 3 | Pull requests live per repo. They share the `issues` row for |
| 4 | title/body/state/timeline; this surface exposes the PR-specific |
| 5 | shape (refs, oids, mergeability, merge state). Reviews and review |
| 6 | comments live in a sibling batch that ships later. |
| 7 | |
| 8 | ## Pull request shape |
| 9 | |
| 10 | ```json |
| 11 | { |
| 12 | "id": 19, |
| 13 | "number": 1, |
| 14 | "title": "wire up foo", |
| 15 | "body": "first cut", |
| 16 | "state": "open", |
| 17 | "draft": false, |
| 18 | "base_ref": "trunk", |
| 19 | "head_ref": "feature", |
| 20 | "base_oid": "abc…", |
| 21 | "head_oid": "def…", |
| 22 | "mergeable": true, |
| 23 | "mergeable_state": "clean", |
| 24 | "merged": false, |
| 25 | "author_id": 42, |
| 26 | "created_at": "2026-05-12T05:00:00Z", |
| 27 | "updated_at": "2026-05-12T05:00:00Z" |
| 28 | } |
| 29 | ``` |
| 30 | |
| 31 | `mergeable` is omitted while the worker hasn't computed it yet |
| 32 | (treat absence as "unknown"). `mergeable_state` mirrors GitHub's |
| 33 | vocabulary (`clean`, `dirty`, `blocked`, `unknown`, `unstable`, |
| 34 | `has_hooks`, etc. — depending on what the worker recorded). |
| 35 | |
| 36 | When `merged` is true, `merge_commit_sha`, `merge_method`, and |
| 37 | `merged_at` are populated. |
| 38 | |
| 39 | ## List pull requests |
| 40 | |
| 41 | ``` |
| 42 | GET /api/v1/repos/{owner}/{repo}/pulls |
| 43 | ``` |
| 44 | |
| 45 | Required scope: `repo:read`. Paginated; `?per_page=` (≤100) and |
| 46 | `?page=` apply, with the standard `Link:` header. |
| 47 | |
| 48 | Optional `?state=open|closed|all` filter (defaults to all); |
| 49 | optional `?draft=true|false` filter. |
| 50 | |
| 51 | ## Get a single pull request |
| 52 | |
| 53 | ``` |
| 54 | GET /api/v1/repos/{owner}/{repo}/pulls/{number} |
| 55 | ``` |
| 56 | |
| 57 | Required scope: `repo:read`. `404` when the number refers to an |
| 58 | issue (use the issues surface) or when the caller can't see the |
| 59 | repo. |
| 60 | |
| 61 | ## Create a pull request |
| 62 | |
| 63 | ``` |
| 64 | POST /api/v1/repos/{owner}/{repo}/pulls |
| 65 | ``` |
| 66 | |
| 67 | Required scope: `repo:write`. Policy: `ActionPullCreate`. |
| 68 | |
| 69 | ```json |
| 70 | { |
| 71 | "title": "wire up foo", |
| 72 | "body": "first cut", |
| 73 | "base": "trunk", |
| 74 | "head": "feature", |
| 75 | "draft": false |
| 76 | } |
| 77 | ``` |
| 78 | |
| 79 | | Field | Type | Notes | |
| 80 | |---------|--------|----------------------------------------------------| |
| 81 | | `title` | string | Required, 1–256 chars. | |
| 82 | | `body` | string | Optional markdown body, ≤65535 chars. | |
| 83 | | `base` | string | Required, branch name on the target repo. | |
| 84 | | `head` | string | Required, must differ from `base`. | |
| 85 | | `draft` | bool | Open as draft; flip to ready via PATCH. | |
| 86 | |
| 87 | Returns `201` with the PR envelope. `422` when `head` or `base` |
| 88 | don't resolve, when they're equal, or when no commits are ahead |
| 89 | of `base`. |
| 90 | |
| 91 | ## Update a pull request |
| 92 | |
| 93 | ``` |
| 94 | PATCH /api/v1/repos/{owner}/{repo}/pulls/{number} |
| 95 | ``` |
| 96 | |
| 97 | Required scope: `repo:write`. Send only the fields you want to |
| 98 | change. |
| 99 | |
| 100 | ```json |
| 101 | { |
| 102 | "title": "renamed", |
| 103 | "body": "updated body", |
| 104 | "state": "closed", |
| 105 | "draft": false |
| 106 | } |
| 107 | ``` |
| 108 | |
| 109 | Permission rules: |
| 110 | |
| 111 | - **Title / body** — PR author OR a repo collaborator with write |
| 112 | access. Other callers `403`. |
| 113 | - **State** — `ActionPullClose` on the repo. |
| 114 | - **Draft** — flipping `true` → `false` (mark ready) is |
| 115 | author-only. `false` → `true` is not supported (`422`). |
| 116 | |
| 117 | ## List commits in a pull request |
| 118 | |
| 119 | ``` |
| 120 | GET /api/v1/repos/{owner}/{repo}/pulls/{number}/commits |
| 121 | ``` |
| 122 | |
| 123 | Required scope: `repo:read`. Returns the commit set ahead of base |
| 124 | in order, populated by the synchronize worker after open. |
| 125 | |
| 126 | ## List files changed by a pull request |
| 127 | |
| 128 | ``` |
| 129 | GET /api/v1/repos/{owner}/{repo}/pulls/{number}/files |
| 130 | ``` |
| 131 | |
| 132 | Required scope: `repo:read`. Each entry carries `path`, `status` |
| 133 | (`added`/`modified`/`removed`/`renamed`), `additions`, |
| 134 | `deletions`, and `changes`. `old_path` is set on renames. |
| 135 | |
| 136 | ## Merge a pull request |
| 137 | |
| 138 | ``` |
| 139 | PUT /api/v1/repos/{owner}/{repo}/pulls/{number}/merge |
| 140 | ``` |
| 141 | |
| 142 | Required scope: `repo:write`. Policy: `ActionPullMerge`. |
| 143 | |
| 144 | ```json |
| 145 | { |
| 146 | "commit_title": "Optional override", |
| 147 | "commit_message": "Optional body", |
| 148 | "merge_method": "merge", |
| 149 | "sha": "<head_oid>" |
| 150 | } |
| 151 | ``` |
| 152 | |
| 153 | | Field | Type | Notes | |
| 154 | |------------------|--------|----------------------------------------------------| |
| 155 | | `commit_title` | string | Subject override; falls back to `"<title> (#<number>)"`. | |
| 156 | | `commit_message` | string | Body for the merge commit. Ignored on rebase. | |
| 157 | | `merge_method` | string | `"merge"`, `"squash"`, `"rebase"` — must be enabled on the repo. Defaults to the repo's default merge method. | |
| 158 | | `sha` | string | Optional `head_oid` guard; `409` on mismatch. | |
| 159 | |
| 160 | Returns `200` with the freshly-loaded PR (state now `closed`, |
| 161 | `merged` true). `409` when the PR is already merged/closed or |
| 162 | when `mergeable_state` isn't `clean`. `422` when the requested |
| 163 | merge method is disabled on the repo or when no commits are |
| 164 | ahead of base. `503` if another merge is in flight. |
| 165 | |
| 166 | ## Reviews |
| 167 | |
| 168 | PR reviews bundle a verdict (`approve`, `request_changes`, or |
| 169 | `comment`) with optional body and any draft inline comments the |
| 170 | caller had pending on this PR. |
| 171 | |
| 172 | ### List reviews |
| 173 | |
| 174 | ``` |
| 175 | GET /api/v1/repos/{owner}/{repo}/pulls/{number}/reviews |
| 176 | ``` |
| 177 | |
| 178 | Required scope: `repo:read`. Returns submitted reviews in |
| 179 | creation order. |
| 180 | |
| 181 | ### Submit a review |
| 182 | |
| 183 | ``` |
| 184 | POST /api/v1/repos/{owner}/{repo}/pulls/{number}/reviews |
| 185 | ``` |
| 186 | |
| 187 | Required scope: `repo:write`. Policy: `ActionPullReview` — |
| 188 | reviewers must be repo collaborators with at least write access. |
| 189 | |
| 190 | ```json |
| 191 | { "event": "APPROVE", "body": "looks good to me" } |
| 192 | ``` |
| 193 | |
| 194 | `event` is one of `APPROVE`, `REQUEST_CHANGES`, `COMMENT` |
| 195 | (lowercase accepted too). Submitting `APPROVE` as the PR author |
| 196 | returns `403`. Submitting attaches every pending inline comment |
| 197 | the caller authored on this PR. |
| 198 | |
| 199 | ## Inline review comments |
| 200 | |
| 201 | ### List |
| 202 | |
| 203 | ``` |
| 204 | GET /api/v1/repos/{owner}/{repo}/pulls/{number}/comments |
| 205 | ``` |
| 206 | |
| 207 | Required scope: `repo:read`. Returns one entry per inline |
| 208 | comment with `file_path`, `side`, `original_commit_sha`, |
| 209 | `original_line`, `original_position`, optional `current_position` |
| 210 | (absent → outdated), `pending`, `resolved`, and threading via |
| 211 | `in_reply_to_id`. |
| 212 | |
| 213 | ### Add |
| 214 | |
| 215 | ``` |
| 216 | POST /api/v1/repos/{owner}/{repo}/pulls/{number}/comments |
| 217 | ``` |
| 218 | |
| 219 | Required scope: `repo:write`. Policy: `ActionPullReview`. |
| 220 | |
| 221 | ```json |
| 222 | { |
| 223 | "body": "nit: unused import", |
| 224 | "file_path": "foo.txt", |
| 225 | "side": "right", |
| 226 | "original_commit_sha": "abc123", |
| 227 | "original_line": 12, |
| 228 | "original_position": 8, |
| 229 | "current_position": 8, |
| 230 | "pending": true |
| 231 | } |
| 232 | ``` |
| 233 | |
| 234 | `pending=true` files a draft that's attached to the next review |
| 235 | the caller submits. `in_reply_to_id` threads under an existing |
| 236 | comment and inherits its diff anchor. |
| 237 | |
| 238 | ## Requested reviewers |
| 239 | |
| 240 | ### List active |
| 241 | |
| 242 | ``` |
| 243 | GET /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers |
| 244 | ``` |
| 245 | |
| 246 | Required scope: `repo:read`. Returns active and historical review |
| 247 | requests with `dismissed_at` / `satisfied_by_review_id` set when |
| 248 | applicable. |
| 249 | |
| 250 | ### Request a reviewer |
| 251 | |
| 252 | ``` |
| 253 | POST /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers |
| 254 | ``` |
| 255 | |
| 256 | Required scope: `repo:write`. Policy: `ActionPullReview`. Body: |
| 257 | |
| 258 | ```json |
| 259 | { "user_id": 42 } |
| 260 | ``` |
| 261 | |
| 262 | or equivalently `{ "username": "bob" }`. `409` when the user is |
| 263 | already an active pending reviewer; `422` on unknown user. |
| 264 | |
| 265 | ### Dismiss a request |
| 266 | |
| 267 | ``` |
| 268 | DELETE /api/v1/repos/{owner}/{repo}/pulls/{number}/requested_reviewers |
| 269 | ``` |
| 270 | |
| 271 | Required scope: `repo:write`. Same body. Returns `204`; `404` |
| 272 | when there is no active request for that user. |
| 273 | |
| 274 | ## Not yet shipped |
| 275 | |
| 276 | - `PUT /pulls/{n}/update-branch` |
| 277 | - `PUT /pulls/{n}/auto-merge` |