@@ -1,27 +1,180 @@ |
| 1 | 1 | # Issues |
| 2 | 2 | |
| 3 | | -> **Planned.** Issues over the API are not yet shipped. The web |
| 4 | | -> UI is the only authoring surface today. |
| 5 | | - |
| 6 | | -## Planned routes |
| 7 | | - |
| 8 | | -| Method | Path | Scope | |
| 9 | | -|--------|--------------------------------------------------------|--------------| |
| 10 | | -| GET | `/api/v1/repos/{owner}/{repo}/issues` | `repo:read` | |
| 11 | | -| GET | `/api/v1/repos/{owner}/{repo}/issues/{number}` | `repo:read` | |
| 12 | | -| POST | `/api/v1/repos/{owner}/{repo}/issues` | `repo` | |
| 13 | | -| PATCH | `/api/v1/repos/{owner}/{repo}/issues/{number}` | `repo` | |
| 14 | | -| GET | `/api/v1/repos/{owner}/{repo}/issues/{number}/comments`| `repo:read` | |
| 15 | | -| POST | `/api/v1/repos/{owner}/{repo}/issues/{number}/comments`| `repo` | |
| 16 | | -| PATCH | `/api/v1/repos/{owner}/{repo}/issues/comments/{id}` | `repo` | |
| 17 | | -| DELETE | `/api/v1/repos/{owner}/{repo}/issues/comments/{id}` | `repo` | |
| 18 | | - |
| 19 | | -Filters on the list endpoint will mirror the web filters |
| 20 | | -(`state`, `author`, `assignee`, `label`, `milestone`, `since`, |
| 21 | | -`sort`, `direction`). |
| 22 | | - |
| 23 | | -## Markdown rendering |
| 24 | | - |
| 25 | | -Posted bodies are stored as raw markdown. Rendering happens at |
| 26 | | -read time, with the same `internal/markdown` pipeline the web UI |
| 27 | | -uses, so an API consumer sees the same HTML the browser would. |
| 3 | +Issues live per repo. They share the `issues` table with pull |
| 4 | +requests, but the REST surface here returns issues only (PRs land |
| 5 | +in their own section). Markdown bodies are stored raw; the cached |
| 6 | +HTML render comes from the same `internal/markdown` pipeline the |
| 7 | +web UI uses. |
| 8 | + |
| 9 | +## Issue shape |
| 10 | + |
| 11 | +```json |
| 12 | +{ |
| 13 | + "id": 17, |
| 14 | + "number": 1, |
| 15 | + "title": "first bug", |
| 16 | + "body": "kaboom", |
| 17 | + "state": "open", |
| 18 | + "state_reason": "", |
| 19 | + "locked": false, |
| 20 | + "lock_reason": "", |
| 21 | + "author_id": 42, |
| 22 | + "labels": ["bug"], |
| 23 | + "created_at": "2026-05-12T05:00:00Z", |
| 24 | + "updated_at": "2026-05-12T05:00:00Z" |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +`state` is `"open"` or `"closed"`. `state_reason` is one of |
| 29 | +`"completed"`, `"not_planned"`, `"duplicate"`, `"reopened"`, or |
| 30 | +empty when no reason has been recorded. `closed_at` is present |
| 31 | +only on closed issues. |
| 32 | + |
| 33 | +## List issues |
| 34 | + |
| 35 | +``` |
| 36 | +GET /api/v1/repos/{owner}/{repo}/issues |
| 37 | +``` |
| 38 | + |
| 39 | +Required scope: `repo:read`. Paginated; `?per_page=` (≤100) and |
| 40 | +`?page=` apply, with the standard `Link:` header. |
| 41 | + |
| 42 | +Optional `?state=open|closed|all` filter; `state=all` (or omitted) |
| 43 | +returns both. |
| 44 | + |
| 45 | +Pull requests are not included on this endpoint — fetch them via |
| 46 | +the pulls surface. |
| 47 | + |
| 48 | +## Get a single issue |
| 49 | + |
| 50 | +``` |
| 51 | +GET /api/v1/repos/{owner}/{repo}/issues/{number} |
| 52 | +``` |
| 53 | + |
| 54 | +Required scope: `repo:read`. `404` when the issue doesn't exist, |
| 55 | +when the caller can't see the repo, or when `{number}` belongs to |
| 56 | +a pull request (use the pulls surface). |
| 57 | + |
| 58 | +## Create an issue |
| 59 | + |
| 60 | +``` |
| 61 | +POST /api/v1/repos/{owner}/{repo}/issues |
| 62 | +``` |
| 63 | + |
| 64 | +Required scope: `repo:write`. Policy: `ActionIssueCreate`. |
| 65 | + |
| 66 | +```json |
| 67 | +{ "title": "first bug", "body": "kaboom" } |
| 68 | +``` |
| 69 | + |
| 70 | +| Field | Type | Notes | |
| 71 | +|---------|--------|-----------------------------------------------| |
| 72 | +| `title` | string | Required, 1–256 chars. | |
| 73 | +| `body` | string | Optional markdown body, ≤65535 chars. | |
| 74 | + |
| 75 | +Returns `201` with the issue envelope. |
| 76 | + |
| 77 | +### Errors |
| 78 | + |
| 79 | +| Status | When | |
| 80 | +|-------:|---------------------------------------------------| |
| 81 | +| 401 | PAT missing/invalid. | |
| 82 | +| 403 | PAT lacks `repo:write` scope or policy denial. | |
| 83 | +| 422 | Empty title, title too long, body too long. | |
| 84 | + |
| 85 | +## Update an issue |
| 86 | + |
| 87 | +``` |
| 88 | +PATCH /api/v1/repos/{owner}/{repo}/issues/{number} |
| 89 | +``` |
| 90 | + |
| 91 | +Required scope: `repo:write`. Only the fields you send are |
| 92 | +modified; everything else stays. |
| 93 | + |
| 94 | +```json |
| 95 | +{ |
| 96 | + "title": "first bug — root cause found", |
| 97 | + "body": "see comment #3", |
| 98 | + "state": "closed", |
| 99 | + "state_reason": "completed" |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +Permission rules: |
| 104 | + |
| 105 | +- **Title / body** — the issue author OR a repo collaborator with |
| 106 | + write access. Other callers `403`. |
| 107 | +- **State / state_reason** — any caller with `ActionIssueClose` |
| 108 | + on the repo. `state_reason` must be one of `completed`, |
| 109 | + `not_planned`, `duplicate`, `reopened` (or empty). |
| 110 | + |
| 111 | +Returns the freshly-loaded issue. |
| 112 | + |
| 113 | +## Lock and unlock |
| 114 | + |
| 115 | +``` |
| 116 | +PUT /api/v1/repos/{owner}/{repo}/issues/{number}/lock |
| 117 | +DELETE /api/v1/repos/{owner}/{repo}/issues/{number}/lock |
| 118 | +``` |
| 119 | + |
| 120 | +Required scope: `repo:write`. PUT body is optional: |
| 121 | + |
| 122 | +```json |
| 123 | +{ "lock_reason": "off-topic" } |
| 124 | +``` |
| 125 | + |
| 126 | +Returns `204`. Locking refuses non-collaborator comments; the |
| 127 | +issue itself stays visible. |
| 128 | + |
| 129 | +## Comments |
| 130 | + |
| 131 | +### List |
| 132 | + |
| 133 | +``` |
| 134 | +GET /api/v1/repos/{owner}/{repo}/issues/{number}/comments |
| 135 | +``` |
| 136 | + |
| 137 | +Required scope: `repo:read`. Returns comments in chronological |
| 138 | +order. |
| 139 | + |
| 140 | +### Add |
| 141 | + |
| 142 | +``` |
| 143 | +POST /api/v1/repos/{owner}/{repo}/issues/{number}/comments |
| 144 | +``` |
| 145 | + |
| 146 | +Required scope: `repo:write`. Policy: `ActionIssueComment`. Body: |
| 147 | + |
| 148 | +```json |
| 149 | +{ "body": "lgtm" } |
| 150 | +``` |
| 151 | + |
| 152 | +Subject to the per-author comment rate limit (20/hour); `429` |
| 153 | +when exceeded. |
| 154 | + |
| 155 | +### Edit own comment |
| 156 | + |
| 157 | +``` |
| 158 | +PATCH /api/v1/repos/{owner}/{repo}/issues/comments/{cid} |
| 159 | +``` |
| 160 | + |
| 161 | +Required scope: `repo:write`. The comment author can edit their |
| 162 | +own; other callers `403`. |
| 163 | + |
| 164 | +### Delete a comment |
| 165 | + |
| 166 | +``` |
| 167 | +DELETE /api/v1/repos/{owner}/{repo}/issues/comments/{cid} |
| 168 | +``` |
| 169 | + |
| 170 | +Required scope: `repo:write`. The comment author can delete their |
| 171 | +own; repo collaborators with write access can delete any comment |
| 172 | +on the repo (moderation affordance, matches the gh shape). |
| 173 | + |
| 174 | +Returns `204`. |
| 175 | + |
| 176 | +## Not yet shipped |
| 177 | + |
| 178 | +`POST /api/v1/repos/{o}/{r}/issues/{n}/transfer`, pinning, |
| 179 | +applying labels / assignees / milestones over the issue PATCH — |
| 180 | +all queued for a follow-up batch. |