Skip to content
githome

Pull Requests API

create, review, and merge pull requests with inline comments, reviews, and diff retrieval

Authentication

All write operations require a bearer token:

Authorization: Bearer <token>

Set Accept: application/vnd.github+json on every request.

Pull request object

A representative PR response:

{
  "id": 88,
  "node_id": "PR_kgDOBc3xAA",
  "number": 17,
  "title": "feat: add sparse checkout support",
  "body": "Implements sparse checkout via the cone pattern mode.\n\nCloses #42.",
  "state": "open",
  "draft": false,
  "locked": false,
  "mergeable": true,
  "merge_commit_sha": null,
  "merged": false,
  "merged_at": null,
  "merged_by": null,
  "mergeStateStatus": "CLEAN",
  "maintainer_can_modify": true,
  "head": {
    "label": "alice:feat/sparse-checkout",
    "ref": "feat/sparse-checkout",
    "sha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "repo": {
      "full_name": "alice/myrepo"
    }
  },
  "base": {
    "label": "alice:main",
    "ref": "main",
    "sha": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
    "repo": {
      "full_name": "alice/myrepo"
    }
  },
  "user": {
    "login": "alice",
    "id": 5,
    "node_id": "U_kgDOAA5",
    "type": "User"
  },
  "assignees": [],
  "labels": [
    {
      "id": 10,
      "node_id": "LA_kgDOAA10",
      "name": "enhancement",
      "color": "a2eeef",
      "description": ""
    }
  ],
  "milestone": null,
  "commits": 3,
  "additions": 120,
  "deletions": 8,
  "changed_files": 5,
  "created_at": "2026-06-01T10:00:00Z",
  "updated_at": "2026-06-09T14:30:00Z",
  "closed_at": null,
  "url": "https://git.example.com/api/v3/repos/alice/myrepo/pulls/17",
  "html_url": "https://git.example.com/alice/myrepo/pull/17",
  "diff_url": "https://git.example.com/alice/myrepo/pull/17.diff",
  "patch_url": "https://git.example.com/alice/myrepo/pull/17.patch"
}

mergeStateStatus values

Value Meaning
CLEAN All checks pass; can merge immediately
DIRTY Merge conflict exists
BEHIND Head is behind base; rebase or merge to update
BLOCKED Branch protection rule prevents merge
DRAFT PR is in draft state
HAS_HOOKS Merge is waiting on pre-receive hooks
UNSTABLE Some checks are failing or pending
UNKNOWN Status cannot be determined yet

Create a pull request

POST /repos/{owner}/{repo}/pulls

Request body fields:

Field Type Required Description
title string yes PR title
head string yes Branch (or user:branch for cross-fork) to merge from
base string yes Branch to merge into
body string no Markdown description
draft boolean no Open as draft (default false)
maintainer_can_modify boolean no Allow maintainers to push to head branch
curl -X POST https://git.example.com/repos/alice/myrepo/pulls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "feat: add sparse checkout support",
    "head": "feat/sparse-checkout",
    "base": "main",
    "body": "Implements sparse checkout via the cone pattern mode.\n\nCloses #42.",
    "draft": false,
    "maintainer_can_modify": true
  }'

Returns 201 Created with the full PR object.

To open a PR from a fork, use the user:branch form for head:

curl -X POST https://git.example.com/repos/alice/myrepo/pulls \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "fix: handle empty tree on first push",
    "head": "bob:fix/empty-tree",
    "base": "main"
  }'

Draft pull requests

Set "draft": true when creating. Draft PRs report mergeStateStatus: DRAFT and cannot be merged until converted to ready.

To convert a draft to ready via GraphQL:

mutation {
  convertPullRequestToDraft(input: { pullRequestId: "PR_kgDOBc3xAA" }) {
    pullRequest {
      number
      isDraft
    }
  }
}
curl -X POST https://git.example.com/api/graphql \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"mutation { convertPullRequestToDraft(input:{pullRequestId:\"PR_kgDOBc3xAA\"}) { pullRequest { number isDraft } } }"}'

List pull requests

GET /repos/{owner}/{repo}/pulls

Query parameters:

Parameter Values Default
state open, closed, all open
sort created, updated, popularity, long-running created
direction asc, desc desc
base base branch name
head user:branch filter
per_page 1-100 30
page integer 1
# all open PRs targeting main
curl "https://git.example.com/repos/alice/myrepo/pulls?state=open&base=main&sort=updated&direction=desc" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"
# merged PRs from a fork user
curl "https://git.example.com/repos/alice/myrepo/pulls?state=closed&head=bob:fix/empty-tree" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Get a pull request

GET /repos/{owner}/{repo}/pulls/{number}
curl https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

The mergeable field is computed asynchronously. On first fetch after a push it may be null; retry after a short delay.

Update a pull request

PATCH /repos/{owner}/{repo}/pulls/{number}

All fields are optional:

# retitle and change base branch
curl -X PATCH https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "feat: add sparse checkout (cone mode only)",
    "base": "develop",
    "maintainer_can_modify": false
  }'
# close without merging
curl -X PATCH https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{"state": "closed"}'

Merge a pull request

PUT /repos/{owner}/{repo}/pulls/{number}/merge

Request body fields:

Field Type Description
commit_title string Title for the merge commit (merge/squash)
commit_message string Extra message for the merge commit
merge_method string merge, squash, or rebase (default merge)
sha string Expected head SHA; fails if head has moved
# merge commit
curl -X PUT https://git.example.com/repos/alice/myrepo/pulls/17/merge \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "commit_title": "feat: add sparse checkout support (#17)",
    "merge_method": "merge",
    "sha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2"
  }'
# squash into one commit
curl -X PUT https://git.example.com/repos/alice/myrepo/pulls/17/merge \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "commit_title": "feat: sparse checkout (squashed)",
    "merge_method": "squash"
  }'
# rebase onto base
curl -X PUT https://git.example.com/repos/alice/myrepo/pulls/17/merge \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{"merge_method": "rebase"}'

Successful response:

{
  "sha": "f1e2d3c4b5a6f1e2d3c4b5a6f1e2d3c4b5a6f1e2",
  "merged": true,
  "message": "Pull Request successfully merged"
}

The server returns 405 Method Not Allowed if mergeStateStatus is not CLEAN, or 409 Conflict if the provided sha does not match the current head.

Files changed

GET /repos/{owner}/{repo}/pulls/{number}/files
curl https://git.example.com/repos/alice/myrepo/pulls/17/files \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Each item in the response array:

{
  "sha": "abc123",
  "filename": "pkg/git/sparse.go",
  "status": "added",
  "additions": 98,
  "deletions": 0,
  "changes": 98,
  "blob_url": "https://git.example.com/alice/myrepo/blob/a1b2c3/pkg/git/sparse.go",
  "raw_url": "https://git.example.com/alice/myrepo/raw/a1b2c3/pkg/git/sparse.go",
  "patch": "@@ -0,0 +1,98 @@\n+package git\n+..."
}

status values: added, modified, removed, renamed, copied, changed, unchanged.

Commits in a pull request

GET /repos/{owner}/{repo}/pulls/{number}/commits
curl https://git.example.com/repos/alice/myrepo/pulls/17/commits \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Returns an array of commit objects in chronological order, same shape as GET /repos/{owner}/{repo}/commits/{sha}.

Retrieve the diff or patch

Pass a content-type accept header to get the raw diff or patch file instead of JSON:

# unified diff
curl https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github.diff" \
  > pr-17.diff
# git-format-patch compatible patch
curl https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github.patch" \
  > pr-17.patch

Reviews

Create a review

POST /repos/{owner}/{repo}/pulls/{number}/reviews
Field Type Description
commit_id string Commit SHA the review is against (defaults to head)
body string Top-level review comment
event string APPROVE, REQUEST_CHANGES, or COMMENT
comments array Inline review comments (see below)

Each inline comment in comments:

Field Type Description
path string File path relative to repo root
position integer Line position in the diff (deprecated, prefer line)
line integer Line number in the file
side string LEFT or RIGHT (default RIGHT)
body string Comment text
curl -X POST https://git.example.com/repos/alice/myrepo/pulls/17/reviews \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Overall looks good. One nit inline.",
    "event": "APPROVE",
    "comments": [
      {
        "path": "pkg/git/sparse.go",
        "line": 42,
        "side": "RIGHT",
        "body": "This allocates on every call. Consider caching the result."
      }
    ]
  }'

Submit a pending review

A review created without an event field is saved as PENDING. Submit it with:

POST /repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}/events
curl -X POST https://git.example.com/repos/alice/myrepo/pulls/17/reviews/55/events \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{"body": "Looks good after the nit is addressed.", "event": "APPROVE"}'

List reviews

GET /repos/{owner}/{repo}/pulls/{number}/reviews
curl https://git.example.com/repos/alice/myrepo/pulls/17/reviews \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Get a review

GET /repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}
curl https://git.example.com/repos/alice/myrepo/pulls/17/reviews/55 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Delete a review

Only PENDING reviews can be deleted.

DELETE /repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}
curl -X DELETE https://git.example.com/repos/alice/myrepo/pulls/17/reviews/55 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Dismiss a review

PUT /repos/{owner}/{repo}/pulls/{number}/reviews/{review_id}/dismissals
curl -X PUT https://git.example.com/repos/alice/myrepo/pulls/17/reviews/55/dismissals \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{"message": "Addressed in follow-up commit."}'

Inline review comments

Inline comments can be created independently of a review.

Create an inline comment

POST /repos/{owner}/{repo}/pulls/{number}/comments
curl -X POST https://git.example.com/repos/alice/myrepo/pulls/17/comments \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "Consider extracting this into a helper.",
    "commit_id": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "path": "pkg/git/sparse.go",
    "line": 67,
    "side": "RIGHT"
  }'

Returns 201 Created.

List inline comments

GET /repos/{owner}/{repo}/pulls/{number}/comments
curl "https://git.example.com/repos/alice/myrepo/pulls/17/comments?sort=created&direction=asc" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Get, update, delete an inline comment

# get
curl https://git.example.com/repos/alice/myrepo/pulls/comments/202 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

# update
curl -X PATCH https://git.example.com/repos/alice/myrepo/pulls/comments/202 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" \
  -H "Content-Type: application/json" \
  -d '{"body": "Consider extracting this into a package-level helper."}'

# delete
curl -X DELETE https://git.example.com/repos/alice/myrepo/pulls/comments/202 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Check status and mergeability

mergeStateStatus in the PR object reflects both branch protection rules and CI check results. To check whether a PR is mergeable before attempting:

STATUS=$(curl -s https://git.example.com/repos/alice/myrepo/pulls/17 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json" | jq -r '.mergeStateStatus')

if [ "$STATUS" = "CLEAN" ]; then
  echo "ready to merge"
else
  echo "cannot merge: $STATUS"
fi

Check runs that feed into mergeStateStatus are accessible at:

curl https://git.example.com/repos/alice/myrepo/commits/a1b2c3d4/check-runs \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"

Search pull requests

The search API covers PRs via the is:pr qualifier:

# open PRs with failing checks
curl "https://git.example.com/search/issues?q=is:pr+is:open+repo:alice/myrepo+status:failure" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/vnd.github+json"