# Pull Requests API Reference

## Core Endpoints

### List Pull Requests

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

**Parameters:**

| Parameter  | Type   | Description                                      |
|------------|--------|--------------------------------------------------|
| state      | string | Filter by state: `open`, `closed`, `all`         |
| sort       | string | Sort by: `oldest`, `recentupdate`, `leastupdate`, `mostcomment`, `leastcomment`, `priority` |
| milestone  | int    | Filter by milestone ID                           |
| labels     | string | Comma-separated label IDs                        |
| poster     | string | Filter by PR author (username)                   |
| page       | int    | Page number (1-based)                            |
| limit      | int    | Page size (default varies by server config)      |

```bash
# List open PRs
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=open&limit=10"

# List closed PRs sorted by most recently updated
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=closed&sort=recentupdate&page=1&limit=25"

# Filter by milestone and labels
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls?state=open&milestone=3&labels=5,12"
```

### Create a Pull Request

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

**CreatePullRequestOption:**

| Field      | Type          | Required | Description                        |
|------------|---------------|----------|------------------------------------|
| title      | string        | yes      | PR title                           |
| body       | string        | no       | PR description (markdown)          |
| head       | string        | yes      | Source branch name                 |
| base       | string        | yes      | Target branch name                 |
| assignee   | string        | no       | Single assignee username           |
| assignees  | array[string] | no       | Multiple assignee usernames        |
| labels     | array[int]    | no       | Label IDs to apply                 |
| milestone  | int           | no       | Milestone ID                       |
| due_date   | string        | no       | Due date (ISO 8601 format)         |

```bash
# Create a simple PR
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Add user authentication",
    "body": "## Summary\nImplements JWT-based auth.\n\nCloses #42",
    "head": "{head_branch}",
    "base": "{base_branch}"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls"

# Create a PR with assignees, labels, and milestone
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Refactor database layer",
    "body": "Switches from raw SQL to ORM queries.",
    "head": "{head_branch}",
    "base": "{base_branch}",
    "assignees": ["{assignee1}", "{assignee2}"],
    "labels": [1, 5, 8],
    "milestone": 2,
    "due_date": "2025-03-15T00:00:00Z"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls"
```

### Get Pull Request Details

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

```bash
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
```

### Update a Pull Request

```
PATCH /repos/{owner}/{repo}/pulls/{index}
```

Accepts the same fields as create (title, body, base, assignee, assignees, labels, milestone, due_date) plus:

| Field                  | Type    | Description                          |
|------------------------|---------|--------------------------------------|
| state                  | string  | Change state: `open` or `closed`     |
| allow_maintainer_edit  | boolean | Allow maintainers to push to head    |

```bash
# Update title and body
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Add user authentication (v2)",
    "body": "Updated implementation using OAuth2."
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"

# Close a PR
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"state": "closed"}' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"

# Allow maintainer edits
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{"allow_maintainer_edit": true}' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}"
```

### List Pinned Pull Requests

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

```bash
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/pinned"
```

### Get PR Between Specific Branches

```
GET /repos/{owner}/{repo}/pulls/{base}/{head}
```

Returns the PR (if any) that merges `head` into `base`.

```bash
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{base}/{head}"
```

### Get Diff or Patch

```
GET /repos/{owner}/{repo}/pulls/{index}.diff
GET /repos/{owner}/{repo}/pulls/{index}.patch
```

Returns raw diff or patch format (plain text, not JSON).

```bash
# Get raw diff
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}.diff"

# Get patch format
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}.patch"
```

### List PR Commits

```
GET /repos/{owner}/{repo}/pulls/{index}/commits
```

```bash
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/commits"
```

### Update PR Branch (Server-Side Rebase/Merge)

```
POST /repos/{owner}/{repo}/pulls/{index}/update
```

Merges or rebases the base branch into the PR's head branch without needing a local clone. Only works when there are no conflicts.

**Query Parameters:**

| Parameter | Type   | Description                            |
|-----------|--------|----------------------------------------|
| style     | string | `rebase` or `merge` (default: `merge`) |

Note: The `style` parameter is a **query parameter** per the Swagger spec. Some Forgejo versions also accept `{"style": "rebase"}` as a JSON body, but the canonical way is as a query parameter.

```bash
# Update PR branch by merging base into head (default)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=merge"

# Update PR branch by rebasing onto base
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
```

---

## PullRequest Response Object

### Key Fields

| Field               | Type          | Description                                           |
|---------------------|---------------|-------------------------------------------------------|
| number              | int           | PR index number                                       |
| title               | string        | PR title                                              |
| body                | string        | PR description (markdown)                             |
| state               | string        | `open` or `closed`                                    |
| draft               | boolean       | Whether this is a draft PR                            |
| mergeable           | boolean/null  | Whether the PR can be merged (`null` = not yet computed) |
| merged              | boolean       | Whether the PR has been merged                        |
| merged_at           | string/null   | Timestamp of merge (ISO 8601)                         |
| merged_by           | User/null     | User who performed the merge                          |
| base                | PRBranchInfo  | Target branch info                                    |
| head                | PRBranchInfo  | Source branch info                                    |
| additions           | int           | Total lines added                                     |
| deletions           | int           | Total lines deleted                                   |
| changed_files       | int           | Number of files changed                               |
| comments            | int           | Number of general comments                            |
| review_comments     | int           | Number of review/inline comments                      |
| labels              | array[Label]  | Applied labels                                        |
| milestone           | Milestone     | Associated milestone                                  |
| assignees           | array[User]   | Assigned users                                        |
| requested_reviewers | array[User]   | Users requested for review                            |
| requested_reviewers_teams | array[Team] | Teams requested for review                         |
| merge_base          | string        | Merge base commit SHA                                 |
| merge_commit_sha    | string        | Merge commit SHA (if merged)                          |
| diff_url            | string        | URL to raw diff                                       |
| patch_url           | string        | URL to patch format                                   |
| allow_maintainer_edit | boolean     | Whether maintainers can push to head branch           |
| pin_order           | int           | Pin order (0 = not pinned)                            |
| flow                | string        | PR flow type                                          |

### PRBranchInfo Fields

| Field    | Type   | Description                     |
|----------|--------|---------------------------------|
| label    | string | Branch label (owner:branch)     |
| ref      | string | Branch name                     |
| sha      | string | Current HEAD SHA of the branch  |
| repo_id  | int    | Repository ID                   |
| repo     | Repository | Full repository object       |

---

## Checking PR Status

### Check if a PR is a Draft

```bash
PR=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}")

IS_DRAFT=$(echo "$PR" | jq '.draft')
echo "Is draft: $IS_DRAFT"
```

### Check if a PR Has Conflicts (Not Mergeable)

The `mergeable` field indicates whether the PR can be cleanly merged:
- `true` - no conflicts, can merge
- `false` - has conflicts, cannot merge
- `null` - mergeability not yet computed (Forgejo computes this lazily)

```bash
MERGEABLE=$(echo "$PR" | jq '.mergeable')
if [ "$MERGEABLE" = "false" ]; then
  echo "PR has merge conflicts!"
elif [ "$MERGEABLE" = "null" ]; then
  echo "Mergeability not yet computed. Try again shortly."
else
  echo "PR is mergeable."
fi
```

### Check if a PR is Stale (Base Branch Has Advanced)

A PR is "stale" when the base branch has new commits that are not in the PR's branch. Compare `pr.base.sha` (the base branch SHA at the time the PR object was generated) with the current HEAD of the base branch:

```bash
# Get the PR's recorded base SHA
PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')

# Get the current HEAD of the base branch
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')
CURRENT_BASE_SHA=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/branches/${BASE_BRANCH}" | jq -r '.commit.id')

if [ "$PR_BASE_SHA" != "$CURRENT_BASE_SHA" ]; then
  echo "PR is stale. Base branch has advanced."
  echo "PR base SHA:      $PR_BASE_SHA"
  echo "Current base SHA:  $CURRENT_BASE_SHA"
  
  # Optionally update the PR branch server-side
  curl -s -X POST \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/pulls/{index}/update?style=rebase"
else
  echo "PR is up to date with base branch."
fi
```

### Combined Status Check

```bash
#!/bin/bash
# Full PR health check
OWNER="{owner}"
REPO="{repo}"
PR_INDEX="{index}"

PR=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/pulls/${PR_INDEX}")

echo "PR #${PR_INDEX}: $(echo "$PR" | jq -r '.title')"
echo "State:     $(echo "$PR" | jq -r '.state')"
echo "Draft:     $(echo "$PR" | jq '.draft')"
echo "Merged:    $(echo "$PR" | jq '.merged')"
echo "Mergeable: $(echo "$PR" | jq '.mergeable')"
echo "Changes:   +$(echo "$PR" | jq '.additions') -$(echo "$PR" | jq '.deletions') in $(echo "$PR" | jq '.changed_files') files"
echo "Comments:  $(echo "$PR" | jq '.comments') general, $(echo "$PR" | jq '.review_comments') review"

# Check staleness
PR_BASE_SHA=$(echo "$PR" | jq -r '.base.sha')
BASE_BRANCH=$(echo "$PR" | jq -r '.base.ref')
CURRENT_BASE=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/${OWNER}/${REPO}/branches/${BASE_BRANCH}" | jq -r '.commit.id')

if [ "$PR_BASE_SHA" != "$CURRENT_BASE" ]; then
  echo "Status:    STALE (base branch has new commits)"
else
  echo "Status:    Up to date"
fi
```
