984 lines
31 KiB
Markdown
984 lines
31 KiB
Markdown
# Gitea REST API Reference
|
|
|
|
Source: gitea-js v1.23.0 TypeScript declarations (generated from Gitea OpenAPI spec).
|
|
Base URL: `https://<host>/api/v1`
|
|
|
|
---
|
|
|
|
## Auth
|
|
|
|
**Header:** `Authorization: token <TOKEN>` — Gitea requires the word `token`, **not** `Bearer`.
|
|
|
|
**Token format:** SHA1 hash string (40 hex chars), e.g. `9fcb1158165773dd010fca5f0cf7174316c3e37d`.
|
|
Returned once on creation via `POST /users/{username}/tokens`; not stored in plain text.
|
|
|
|
**Token scopes** (Gitea 1.19+): fine-grained scopes like `read:issue`, `write:repository`, etc.
|
|
Older tokens have no scopes and are effectively admin-level for the user.
|
|
|
|
---
|
|
|
|
## Pagination
|
|
|
|
All list endpoints use `page` (1-based, default 1) and `limit` (page size).
|
|
**Not** `per_page` — GitHub uses `per_page`, Gitea uses `limit`.
|
|
|
|
**Response header:** `x-total-count` (lowercase) — total item count across all pages.
|
|
Also returns a `Link` header with `rel="next"` / `rel="last"` URLs.
|
|
|
|
---
|
|
|
|
## Issues
|
|
|
|
### List Issues
|
|
```
|
|
GET /repos/{owner}/{repo}/issues
|
|
```
|
|
| Param | Type | Notes |
|
|
|---|---|---|
|
|
| `state` | `open\|closed\|all` | default `open` |
|
|
| `type` | `issues\|pulls` | filter by type |
|
|
| `labels` | string | comma-separated label names |
|
|
| `milestones` | string | comma-separated milestone names or IDs |
|
|
| `since` | date-time | RFC 3339; updated after |
|
|
| `before` | date-time | RFC 3339; updated before |
|
|
| `created_by` | string | filter by creator username |
|
|
| `assigned_by` | string | filter by assignee username |
|
|
| `mentioned_by` | string | filter by mentioned username |
|
|
| `page` | int | 1-based |
|
|
| `limit` | int | page size |
|
|
|
|
Returns: `Issue[]`
|
|
|
|
### Search Issues (cross-repo)
|
|
```
|
|
GET /repos/issues/search
|
|
```
|
|
| Param | Type | Notes |
|
|
|---|---|---|
|
|
| `state` | `open\|closed\|all` | default `open` |
|
|
| `type` | `issues\|pulls` | |
|
|
| `labels` | string | comma-separated |
|
|
| `milestones` | string | comma-separated |
|
|
| `q` | string | search string |
|
|
| `priority_repo_id` | int64 | repo ID to rank higher |
|
|
| `since` / `before` | date-time | |
|
|
| `assigned` | bool | assigned to authed user |
|
|
| `created` | bool | created by authed user |
|
|
| `mentioned` | bool | mentioning authed user |
|
|
| `review_requested` | bool | review requested from authed user |
|
|
| `reviewed` | bool | reviewed by authed user |
|
|
| `owner` | string | filter by repo owner |
|
|
| `team` | string | requires `owner` |
|
|
| `page` / `limit` | int | |
|
|
|
|
Returns: `Issue[]`
|
|
|
|
### Get Issue
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}
|
|
```
|
|
Returns: `Issue`
|
|
|
|
### Create Issue
|
|
```
|
|
POST /repos/{owner}/{repo}/issues
|
|
```
|
|
Body: `CreateIssueOption`
|
|
|
|
| Field | Type | Required | Notes |
|
|
|---|---|---|---|
|
|
| `title` | string | yes | |
|
|
| `body` | string | no | |
|
|
| `assignees` | string[] | no | usernames |
|
|
| `assignee` | string | no | deprecated, use `assignees` |
|
|
| `milestone` | int64 | no | milestone ID |
|
|
| `labels` | number[] | no | label IDs |
|
|
| `due_date` | date-time | no | only date part used |
|
|
| `closed` | bool | no | create already-closed |
|
|
| `ref` | string | no | branch/commit ref |
|
|
|
|
Returns: `Issue` (HTTP 201)
|
|
|
|
### Edit Issue (close/reopen/update)
|
|
```
|
|
PATCH /repos/{owner}/{repo}/issues/{index}
|
|
```
|
|
Body: `EditIssueOption`
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `title` | string | |
|
|
| `body` | string | |
|
|
| `state` | string | `"open"` or `"closed"` — this is how you close/reopen |
|
|
| `assignees` | string[] | replaces all assignees |
|
|
| `assignee` | string | deprecated |
|
|
| `milestone` | int64 | milestone ID (0 to clear) |
|
|
| `due_date` | date-time | |
|
|
| `unset_due_date` | bool | set true to clear deadline |
|
|
| `ref` | string | |
|
|
|
|
Returns: `Issue`
|
|
|
|
### Delete Issue
|
|
```
|
|
DELETE /repos/{owner}/{repo}/issues/{index}
|
|
```
|
|
Returns: HTTP 204 (requires admin/owner)
|
|
|
|
### Pin / Unpin Issue
|
|
```
|
|
POST /repos/{owner}/{repo}/issues/{index}/pin
|
|
DELETE /repos/{owner}/{repo}/issues/{index}/pin
|
|
```
|
|
No body. Returns: HTTP 204
|
|
|
|
### Move Pin Position
|
|
```
|
|
PATCH /repos/{owner}/{repo}/issues/{index}/pin/{position}
|
|
```
|
|
`position` is a 1-based integer. Returns: HTTP 204
|
|
|
|
### List Pinned Issues
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/pinned
|
|
```
|
|
Returns: `Issue[]`
|
|
|
|
### Check New Pin Allowed
|
|
```
|
|
GET /repos/{owner}/{repo}/new_pin_allowed
|
|
```
|
|
Returns: `NewIssuePinsAllowed { issues: bool, pull_requests: bool }`
|
|
|
|
---
|
|
|
|
## Issue Object Schema
|
|
|
|
```typescript
|
|
interface Issue {
|
|
id?: number; // global DB ID (not the display number)
|
|
number?: number; // repo-scoped issue number (use this in URLs)
|
|
title?: string;
|
|
body?: string;
|
|
state?: StateType; // "open" | "closed"
|
|
user?: User; // creator
|
|
assignee?: User;
|
|
assignees?: User[];
|
|
labels?: Label[];
|
|
milestone?: Milestone;
|
|
comments?: number; // comment count
|
|
created_at?: string; // ISO 8601
|
|
updated_at?: string;
|
|
closed_at?: string;
|
|
due_date?: string;
|
|
pull_request?: PullRequestMeta; // non-null if this issue is a PR
|
|
is_locked?: boolean;
|
|
pin_order?: number; // 0 if not pinned; position otherwise
|
|
ref?: string;
|
|
repository?: RepositoryMeta;
|
|
original_author?: string; // for migrated issues
|
|
original_author_id?: number;
|
|
html_url?: string;
|
|
url?: string;
|
|
assets?: Attachment[];
|
|
}
|
|
```
|
|
|
|
**Gitea-specific vs GitHub:**
|
|
- `number` is the repo-scoped index; `id` is the global DB ID. GitHub calls the display number `number` too, but Gitea has both.
|
|
- `pin_order` — no GitHub equivalent.
|
|
- `original_author` / `original_author_id` — for migrated content, no GitHub equivalent.
|
|
- `is_locked` is present but there's no dedicated lock/unlock endpoint in the public API.
|
|
- `due_date` — Gitea has native deadline support; GitHub does not.
|
|
- `StateType` is typed as `string` in TypeScript; values are `"open"` and `"closed"`.
|
|
|
|
---
|
|
|
|
## Issue Dependencies (Blocking)
|
|
|
|
Gitea models two directions: A **blocks** B (A must be resolved before B), and A **depends on** B.
|
|
From any issue's perspective: `/blocks` = issues that this issue blocks, `/dependencies` = issues that block this issue.
|
|
|
|
### List issues blocked BY this issue (this issue blocks them)
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}/blocks
|
|
```
|
|
Query: `page`, `limit`
|
|
Returns: `Issue[]` — the downstream issues that can't proceed until `{index}` is resolved.
|
|
|
|
### Add a blocking relationship (make this issue block another)
|
|
```
|
|
POST /repos/{owner}/{repo}/issues/{index}/blocks
|
|
```
|
|
Body: `IssueMeta { owner: string, repo: string, index: number }`
|
|
|
|
Returns: `Issue` (the issue that is now blocked)
|
|
|
|
**Note:** `{index}` in the URL path is typed as `string` in gitea-js (accepts number as string).
|
|
Body `IssueMeta.index` is `number`.
|
|
|
|
### Remove a blocking relationship
|
|
```
|
|
DELETE /repos/{owner}/{repo}/issues/{index}/blocks
|
|
```
|
|
Body: `IssueMeta { owner: string, repo: string, index: number }`
|
|
Returns: `Issue`
|
|
|
|
### List dependencies of this issue (issues that block this one)
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}/dependencies
|
|
```
|
|
Query: `page`, `limit`
|
|
Returns: `Issue[]` — issues that must be resolved before `{index}` can proceed.
|
|
|
|
### Add a dependency (make this issue depend on another)
|
|
```
|
|
POST /repos/{owner}/{repo}/issues/{index}/dependencies
|
|
```
|
|
Body: `IssueMeta { owner: string, repo: string, index: number }`
|
|
Returns: `Issue`
|
|
|
|
### Remove a dependency
|
|
```
|
|
DELETE /repos/{owner}/{repo}/issues/{index}/dependencies
|
|
```
|
|
Body: `IssueMeta { owner: string, repo: string, index: number }`
|
|
Returns: `Issue`
|
|
|
|
**Terminology clarification:**
|
|
- `GET /issues/{index}/blocks` → "issues blocked by {index}" = downstream dependents
|
|
- `GET /issues/{index}/dependencies` → "issues blocking {index}" = upstream blockers
|
|
- GitHub has no equivalent API; this is Gitea-only.
|
|
|
|
---
|
|
|
|
## Issue Comments
|
|
|
|
### List comments on an issue
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}/comments
|
|
```
|
|
| Param | Notes |
|
|
|---|---|
|
|
| `since` | date-time, RFC 3339 |
|
|
| `before` | date-time, RFC 3339 |
|
|
|
|
Returns: `Comment[]`
|
|
Note: no `page`/`limit` on this specific endpoint (lists all comments).
|
|
|
|
### List all comments in a repo
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/comments
|
|
```
|
|
Query: `since`, `before`, `page`, `limit`
|
|
Returns: `Comment[]`
|
|
|
|
### Get single comment
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/comments/{id}
|
|
```
|
|
Note: comment ID is the global DB ID, not a per-issue sequence.
|
|
Returns: `Comment`
|
|
|
|
### Create comment
|
|
```
|
|
POST /repos/{owner}/{repo}/issues/{index}/comments
|
|
```
|
|
Body: `{ body: string }` (required)
|
|
Returns: `Comment`
|
|
|
|
### Edit comment
|
|
```
|
|
PATCH /repos/{owner}/{repo}/issues/comments/{id}
|
|
```
|
|
Body: `{ body: string }` (required)
|
|
Returns: `Comment`
|
|
|
|
Deprecated variant: `PATCH /repos/{owner}/{repo}/issues/{index}/comments/{id}`
|
|
|
|
### Delete comment
|
|
```
|
|
DELETE /repos/{owner}/{repo}/issues/comments/{id}
|
|
```
|
|
Deprecated variant: `DELETE /repos/{owner}/{repo}/issues/{index}/comments/{id}`
|
|
|
|
### Comment schema
|
|
```typescript
|
|
interface Comment {
|
|
id?: number; // global DB ID
|
|
body?: string;
|
|
user?: User;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
html_url?: string;
|
|
issue_url?: string;
|
|
pull_request_url?: string;
|
|
original_author?: string;
|
|
original_author_id?: number;
|
|
assets?: Attachment[];
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Issue Timeline (comments + events)
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}/timeline
|
|
```
|
|
Query: `since`, `before`, `page`, `limit`
|
|
Returns: `TimelineComment[]` — includes all events (label changes, state changes, etc.) not just text comments.
|
|
|
|
---
|
|
|
|
## Labels
|
|
|
|
### Repo label CRUD
|
|
```
|
|
GET /repos/{owner}/{repo}/labels → Label[] (page, limit)
|
|
POST /repos/{owner}/{repo}/labels → Label (CreateLabelOption)
|
|
GET /repos/{owner}/{repo}/labels/{id} → Label
|
|
PATCH /repos/{owner}/{repo}/labels/{id} → Label (EditLabelOption)
|
|
DELETE /repos/{owner}/{repo}/labels/{id} → 204
|
|
```
|
|
|
|
### Label schema
|
|
```typescript
|
|
interface Label {
|
|
id?: number;
|
|
name?: string;
|
|
color?: string; // hex without #, e.g. "00aabb"
|
|
description?: string;
|
|
exclusive?: boolean; // Gitea-only: exclusive label (scoped labels)
|
|
is_archived?: boolean; // Gitea-only: archived/hidden label
|
|
url?: string;
|
|
}
|
|
```
|
|
|
|
**Gitea-specific:** `exclusive` labels are scoped — only one label with `exclusive=true` in a group can be applied at a time (like GitHub's scoped labels, but implemented differently). `is_archived` hides labels from UI while preserving existing uses.
|
|
|
|
### CreateLabelOption
|
|
```typescript
|
|
{ color: string, name: string, description?: string, exclusive?: boolean, is_archived?: boolean }
|
|
```
|
|
`color` must include the `#`, e.g. `"#00aabb"`.
|
|
|
|
### EditLabelOption
|
|
```typescript
|
|
{ color?: string, name?: string, description?: string, exclusive?: boolean, is_archived?: boolean }
|
|
```
|
|
|
|
### Labels on Issues/PRs
|
|
|
|
```
|
|
GET /repos/{owner}/{repo}/issues/{index}/labels → Label[]
|
|
POST /repos/{owner}/{repo}/issues/{index}/labels → Label[] (add labels)
|
|
PUT /repos/{owner}/{repo}/issues/{index}/labels → Label[] (replace all labels)
|
|
DELETE /repos/{owner}/{repo}/issues/{index}/labels → 204 (remove ALL labels)
|
|
DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} → 204 (remove one label)
|
|
```
|
|
|
|
Body for POST and PUT: `IssueLabelsOption`
|
|
```typescript
|
|
interface IssueLabelsOption {
|
|
labels?: (number | string)[]; // label IDs or label names (mixed array supported)
|
|
}
|
|
```
|
|
|
|
**Gitea-specific:** Labels can be specified by ID (int) or by name (string) in the same array.
|
|
GitHub only supports IDs.
|
|
|
|
---
|
|
|
|
## Milestones
|
|
|
|
```
|
|
GET /repos/{owner}/{repo}/milestones → Milestone[]
|
|
POST /repos/{owner}/{repo}/milestones → Milestone
|
|
GET /repos/{owner}/{repo}/milestones/{id} → Milestone
|
|
PATCH /repos/{owner}/{repo}/milestones/{id} → Milestone
|
|
DELETE /repos/{owner}/{repo}/milestones/{id} → 204
|
|
```
|
|
|
|
List query params: `state` (`open|closed|all`), `name` (filter by name), `page`, `limit`.
|
|
|
|
### Milestone schema
|
|
```typescript
|
|
interface Milestone {
|
|
id?: number;
|
|
title?: string;
|
|
description?: string;
|
|
state?: StateType; // "open" | "closed"
|
|
open_issues?: number;
|
|
closed_issues?: number;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
closed_at?: string;
|
|
due_on?: string; // Note: GitHub calls this "due_on" too
|
|
}
|
|
```
|
|
|
|
### CreateMilestoneOption
|
|
```typescript
|
|
{ title?: string, description?: string, due_on?: date-time, state?: 'open'|'closed' }
|
|
```
|
|
|
|
### EditMilestoneOption
|
|
```typescript
|
|
{ title?: string, description?: string, due_on?: date-time, state?: string }
|
|
```
|
|
|
|
---
|
|
|
|
## Pull Requests
|
|
|
|
### List PRs
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls
|
|
```
|
|
| Param | Type | Notes |
|
|
|---|---|---|
|
|
| `state` | `open\|closed\|all` | default `open` |
|
|
| `sort` | string | `oldest\|recentupdate\|leastupdate\|mostcomment\|leastcomment\|priority` |
|
|
| `milestone` | int64 | milestone ID |
|
|
| `labels` | number[] | label IDs |
|
|
| `poster` | string | filter by PR author username |
|
|
| `page` | int | 1-based |
|
|
| `limit` | int | |
|
|
|
|
Returns: `PullRequest[]`
|
|
|
|
### Get PR
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}
|
|
```
|
|
Returns: `PullRequest`
|
|
|
|
### Get PR by base and head
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{base}/{head}
|
|
```
|
|
Returns: `PullRequest`
|
|
|
|
### Create PR
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls
|
|
```
|
|
Body: `CreatePullRequestOption`
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `title` | string | |
|
|
| `body` | string | |
|
|
| `head` | string | source branch (or `fork:branch`) |
|
|
| `base` | string | target branch |
|
|
| `assignees` | string[] | |
|
|
| `assignee` | string | deprecated |
|
|
| `labels` | number[] | label IDs |
|
|
| `milestone` | int64 | |
|
|
| `reviewers` | string[] | usernames |
|
|
| `team_reviewers` | string[] | team slugs |
|
|
| `due_date` | date-time | |
|
|
| `allow_maintainer_edit` | bool | (not in gitea-js CreatePullRequestOption but supported in API) |
|
|
|
|
**Draft PR:** The gitea-js `CreatePullRequestOption` does not include a `draft` field.
|
|
The underlying Go struct (`CreatePullRequestOption`) also has no `draft` field as of v1.23.
|
|
**There is no API to create a draft PR or convert draft to ready.** This is a known Gitea limitation.
|
|
|
|
Returns: `PullRequest`
|
|
|
|
### Edit PR
|
|
```
|
|
PATCH /repos/{owner}/{repo}/pulls/{index}
|
|
```
|
|
Body: `EditPullRequestOption`
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `title` | string | |
|
|
| `body` | string | |
|
|
| `state` | string | `"open"` or `"closed"` to close/reopen |
|
|
| `base` | string | change target branch |
|
|
| `assignees` | string[] | replaces all |
|
|
| `assignee` | string | deprecated |
|
|
| `labels` | number[] | replaces all |
|
|
| `milestone` | int64 | |
|
|
| `due_date` | date-time | |
|
|
| `unset_due_date` | bool | clear deadline |
|
|
| `allow_maintainer_edit` | bool | |
|
|
|
|
**No `draft` or `ready_for_review` field.** Draft status cannot be changed via API.
|
|
|
|
Returns: `PullRequest`
|
|
|
|
### Get PR diff / patch
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}.{diffType}
|
|
```
|
|
`diffType`: `diff` or `patch`
|
|
Query: `binary` (bool) — include binary changes (makes patch applicable via `git apply`)
|
|
Returns: raw string
|
|
|
|
### Get PR commits
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/commits
|
|
```
|
|
Query: `page`, `limit`, `verification` (bool, default true), `files` (bool, default true)
|
|
Returns: `Commit[]`
|
|
|
|
### Get changed files
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/files
|
|
```
|
|
| Param | Notes |
|
|
|---|---|
|
|
| `skip-to` | filename to start from (cursor-style) |
|
|
| `whitespace` | `ignore-all\|ignore-change\|ignore-eol\|show-all` |
|
|
| `page`, `limit` | |
|
|
|
|
Returns: `ChangedFile[]`
|
|
|
|
### Check if merged
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/merge
|
|
```
|
|
Returns: HTTP 204 if merged, HTTP 404 if not.
|
|
|
|
### Merge PR
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/merge
|
|
```
|
|
Body: `MergePullRequestOption`
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `Do` | string | **required**: `merge\|rebase\|rebase-merge\|squash\|fast-forward-only\|manually-merged` |
|
|
| `MergeCommitID` | string | for `manually-merged` |
|
|
| `MergeMessageField` | string | commit message body |
|
|
| `MergeTitleField` | string | commit message title |
|
|
| `delete_branch_after_merge` | bool | |
|
|
| `force_merge` | bool | override merge checks |
|
|
| `head_commit_id` | string | guard against race condition |
|
|
| `merge_when_checks_succeed` | bool | schedule auto-merge |
|
|
|
|
**Gitea-specific:** `manually-merged` value marks an already-merged PR without actually merging.
|
|
`fast-forward-only` is supported (not in GitHub API).
|
|
|
|
### Cancel auto-merge
|
|
```
|
|
DELETE /repos/{owner}/{repo}/pulls/{index}/merge
|
|
```
|
|
|
|
### Update PR branch (merge base into head)
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/update
|
|
```
|
|
Query: `style` — `merge` or `rebase`
|
|
|
|
### PullRequest schema
|
|
```typescript
|
|
interface PullRequest {
|
|
id?: number; // global DB ID
|
|
number?: number; // repo-scoped PR number
|
|
title?: string;
|
|
body?: string;
|
|
state?: StateType; // "open" | "closed"
|
|
draft?: boolean; // Gitea-specific: draft PR flag (read-only via API)
|
|
user?: User;
|
|
assignee?: User;
|
|
assignees?: User[];
|
|
labels?: Label[];
|
|
milestone?: Milestone;
|
|
base?: PRBranchInfo; // target branch info
|
|
head?: PRBranchInfo; // source branch info
|
|
merge_base?: string; // SHA of common ancestor
|
|
merge_commit_sha?: string; // SHA of merge commit (null if not merged)
|
|
merged?: boolean;
|
|
merged_at?: string;
|
|
merged_by?: User;
|
|
mergeable?: boolean;
|
|
allow_maintainer_edit?: boolean;
|
|
requested_reviewers?: User[];
|
|
requested_reviewers_teams?: Team[];
|
|
comments?: number;
|
|
review_comments?: number; // diff-level review comments only
|
|
additions?: number;
|
|
deletions?: number;
|
|
changed_files?: number;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
closed_at?: string;
|
|
due_date?: string;
|
|
diff_url?: string;
|
|
patch_url?: string;
|
|
html_url?: string;
|
|
url?: string;
|
|
is_locked?: boolean;
|
|
pin_order?: number;
|
|
}
|
|
|
|
interface PRBranchInfo {
|
|
label?: string; // "owner:branch"
|
|
ref?: string; // branch name
|
|
sha?: string; // HEAD SHA
|
|
repo_id?: number;
|
|
repo?: Repository;
|
|
}
|
|
```
|
|
|
|
**Gitea-specific vs GitHub:**
|
|
- `draft` is present but **read-only** — cannot be set/changed via API.
|
|
- `pin_order` — no GitHub equivalent.
|
|
- `allow_maintainer_edit` — GitHub calls this `maintainer_can_modify`.
|
|
- GitHub has `review_decision` (computed field); Gitea does not — you must compute approval state from the reviews list.
|
|
- GitHub uses `head.repo` and `base.repo` as nested objects; Gitea uses the same pattern via `PRBranchInfo.repo`.
|
|
- `due_date` — Gitea-only deadline field.
|
|
|
|
---
|
|
|
|
## PR Reviews
|
|
|
|
### List reviews
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/reviews
|
|
```
|
|
Query: `page`, `limit`
|
|
Returns: `PullReview[]`
|
|
|
|
### Create review (or start a pending review)
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/reviews
|
|
```
|
|
Body: `CreatePullReviewOptions`
|
|
|
|
| Field | Type | Notes |
|
|
|---|---|---|
|
|
| `event` | ReviewStateType | `APPROVED\|REQUEST_CHANGES\|COMMENT\|PENDING` |
|
|
| `body` | string | overall review comment |
|
|
| `commit_id` | string | commit to review at (defaults to head) |
|
|
| `comments` | CreatePullReviewComment[] | inline diff comments |
|
|
|
|
`CreatePullReviewComment`:
|
|
```typescript
|
|
{
|
|
path?: string; // file path
|
|
body?: string; // comment text
|
|
new_position?: number; // line in new file (0 = not a line comment)
|
|
old_position?: number; // line in old file (0 = not a line comment)
|
|
}
|
|
```
|
|
|
|
To create a **pending** review (accumulate comments before submitting), pass `event: "PENDING"` or omit `event`.
|
|
Returns: `PullReview`
|
|
|
|
### Get a review
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}
|
|
```
|
|
Returns: `PullReview`
|
|
|
|
### Submit (publish) a pending review
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}
|
|
```
|
|
Body: `SubmitPullReviewOptions`
|
|
```typescript
|
|
{ body?: string, event?: ReviewStateType }
|
|
```
|
|
`event` values: `APPROVED`, `REQUEST_CHANGES`, `COMMENT`
|
|
Returns: `PullReview`
|
|
|
|
### Delete a review
|
|
```
|
|
DELETE /repos/{owner}/{repo}/pulls/{index}/reviews/{id}
|
|
```
|
|
Returns: HTTP 204
|
|
|
|
### Get review inline comments
|
|
```
|
|
GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments
|
|
```
|
|
Returns: `PullReviewComment[]`
|
|
|
|
### Dismiss a review
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals
|
|
```
|
|
Body: `DismissPullReviewOptions`
|
|
```typescript
|
|
{ message?: string, priors?: boolean }
|
|
```
|
|
`priors: true` dismisses all prior reviews from the same reviewer.
|
|
Returns: `PullReview`
|
|
|
|
### Un-dismiss a review
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismissals
|
|
```
|
|
Returns: `PullReview`
|
|
|
|
### Request / cancel review requests
|
|
```
|
|
POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers → PullReview[]
|
|
DELETE /repos/{owner}/{repo}/pulls/{index}/requested_reviewers → 204
|
|
```
|
|
Body: `PullReviewRequestOptions { reviewers?: string[], team_reviewers?: string[] }`
|
|
|
|
### PullReview schema
|
|
```typescript
|
|
interface PullReview {
|
|
id?: number;
|
|
user?: User;
|
|
team?: Team; // for team review requests
|
|
body?: string; // overall comment text
|
|
state?: ReviewStateType; // "APPROVED" | "REQUEST_CHANGES" | "COMMENT" | "PENDING"
|
|
commit_id?: string; // commit SHA the review is on
|
|
submitted_at?: string;
|
|
updated_at?: string;
|
|
stale?: boolean; // GITEA-SPECIFIC: true if head has moved since review
|
|
official?: boolean; // GITEA-SPECIFIC: counts toward required approvals
|
|
dismissed?: boolean;
|
|
comments_count?: number;
|
|
html_url?: string;
|
|
pull_request_url?: string;
|
|
}
|
|
```
|
|
|
|
**Gitea-specific vs GitHub:**
|
|
- `official` — whether this review counts toward branch protection required approvals. No GitHub equivalent.
|
|
- `stale` — whether the PR head has been updated since this review was submitted, making it outdated. GitHub shows this in UI but doesn't expose it as a field.
|
|
- **No `reviewDecision` equivalent** — Gitea has no computed "overall review decision" field on the PR object. You must fetch all reviews and compute:
|
|
- Count `APPROVED` reviews where `official=true` and `stale=false` and `dismissed=false`.
|
|
- Check if any `REQUEST_CHANGES` review is `official=true` and not dismissed.
|
|
- `ReviewStateType` string values: `"APPROVED"`, `"REQUEST_CHANGES"`, `"COMMENT"`, `"PENDING"`
|
|
|
|
### PullReviewComment schema
|
|
```typescript
|
|
interface PullReviewComment {
|
|
id?: number;
|
|
body?: string;
|
|
path?: string;
|
|
position?: number; // line in new file
|
|
original_position?: number; // line in old file
|
|
diff_hunk?: string; // context diff around the comment
|
|
commit_id?: string;
|
|
original_commit_id?: string;
|
|
pull_request_review_id?: number;
|
|
pull_request_url?: string;
|
|
user?: User;
|
|
resolver?: User; // GITEA-SPECIFIC: user who resolved the thread
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
html_url?: string;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Commit Statuses (CI checks)
|
|
|
|
Gitea uses commit statuses (like GitHub's commit status API), **not** GitHub Check Runs.
|
|
There is no Gitea equivalent to `GET /check-runs` or `GET /check-suites`.
|
|
|
|
### List statuses by ref (branch/tag/commit)
|
|
```
|
|
GET /repos/{owner}/{repo}/commits/{ref}/statuses
|
|
```
|
|
| Param | Notes |
|
|
|---|---|
|
|
| `sort` | `oldest\|recentupdate\|leastupdate\|leastindex\|highestindex` |
|
|
| `state` | `pending\|success\|error\|failure\|warning` |
|
|
| `page`, `limit` | |
|
|
|
|
Returns: `CommitStatus[]`
|
|
|
|
### Get combined status by ref
|
|
```
|
|
GET /repos/{owner}/{repo}/commits/{ref}/status
|
|
```
|
|
Returns: `CombinedStatus`
|
|
|
|
```typescript
|
|
interface CombinedStatus {
|
|
sha?: string;
|
|
state?: CommitStatusState; // overall: worst of all statuses
|
|
statuses?: CommitStatus[];
|
|
total_count?: number;
|
|
repository?: Repository;
|
|
commit_url?: string;
|
|
url?: string;
|
|
}
|
|
```
|
|
|
|
### List statuses by SHA
|
|
```
|
|
GET /repos/{owner}/{repo}/statuses/{sha}
|
|
```
|
|
Same query params as above. Returns: `CommitStatus[]`
|
|
|
|
### Create a commit status
|
|
```
|
|
POST /repos/{owner}/{repo}/statuses/{sha}
|
|
```
|
|
Body: `CreateStatusOption`
|
|
```typescript
|
|
{
|
|
context?: string; // e.g. "ci/test" — identifies the check
|
|
state?: CommitStatusState; // "pending" | "success" | "error" | "failure"
|
|
description?: string;
|
|
target_url?: string; // link to build/CI page
|
|
}
|
|
```
|
|
Returns: `CommitStatus`
|
|
|
|
### CommitStatus schema
|
|
```typescript
|
|
interface CommitStatus {
|
|
id?: number;
|
|
context?: string; // identifier, e.g. "ci/build"
|
|
status?: CommitStatusState; // "pending" | "success" | "error" | "failure"
|
|
description?: string;
|
|
target_url?: string;
|
|
creator?: User;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
url?: string;
|
|
}
|
|
```
|
|
|
|
**CommitStatusState values:** `"pending"`, `"success"`, `"error"`, `"failure"`, `"warning"`, `"skipped"`
|
|
Note: `"warning"` is valid in list/filter but the spec comment says "pending, success, error and failure" for CreateStatusOption — `warning` may not be creatable.
|
|
Note: `"skipped"` exists in current Gitea server code but is absent from older OpenAPI specs; older instances never emit it.
|
|
Gitea's own combine logic treats `warning` as a failing state and `skipped` as compatible with success.
|
|
|
|
**For PR CI status:** Use `GET /repos/{owner}/{repo}/commits/{ref}/status` where `ref` is the PR head SHA (`pr.head.sha`).
|
|
|
|
---
|
|
|
|
## Projects
|
|
|
|
Gitea has a projects feature (kanban boards) but **no REST API endpoints for projects** are exposed as of v1.23.
|
|
- `project_id` appears in `TimelineComment` (for "moved to project" events) but there's no CRUD API.
|
|
- Repository settings expose `has_projects` (bool) and `projects_mode` (`"repo"|"owner"|"all"`).
|
|
- Project management must be done through the web UI.
|
|
|
|
**GitHub comparison:** GitHub has a full Projects v2 GraphQL API and Projects REST API. Gitea has neither.
|
|
|
|
---
|
|
|
|
## Draft Pull Requests
|
|
|
|
Gitea supports draft PRs in the web UI but has **significant API limitations:**
|
|
|
|
1. **Cannot create a draft PR via API** — `CreatePullRequestOption` has no `draft` field.
|
|
2. **Cannot convert draft to ready via API** — `EditPullRequestOption` has no `draft` or `ready_for_review` field.
|
|
3. **Can read draft status** — `PullRequest.draft` is a readable boolean field.
|
|
4. **Workaround:** Title prefix convention — some users prefix draft PR titles with `[WIP]` or `Draft:` and remove the prefix to signal readiness, but this is not enforced by the API.
|
|
|
|
The `op_type` enum in activity includes `pull_request_ready_for_review`, indicating the feature exists in the event log, but no API to trigger this transition is exposed.
|
|
|
|
---
|
|
|
|
## PR Comments (Review Comments vs Issue Comments)
|
|
|
|
Gitea separates:
|
|
|
|
1. **Issue-style PR comments** (general comments, not tied to diff lines):
|
|
- `GET/POST /repos/{owner}/{repo}/issues/{index}/comments`
|
|
- Same `Comment` schema as issue comments.
|
|
|
|
2. **Review comments** (diff-level, tied to a review):
|
|
- `GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments`
|
|
- Created as part of `CreatePullReviewOptions.comments[]`.
|
|
- Schema: `PullReviewComment`.
|
|
|
|
There is no endpoint to create a standalone review comment outside of a review (unlike GitHub's `POST /pulls/{index}/comments`).
|
|
|
|
---
|
|
|
|
## Complete Endpoint Index (Relevant to gitea-axi)
|
|
|
|
### Issues
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/repos/{owner}/{repo}/issues` | list; `type=issues` for issues only |
|
|
| POST | `/repos/{owner}/{repo}/issues` | create |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}` | get |
|
|
| PATCH | `/repos/{owner}/{repo}/issues/{index}` | edit; use `state` to close/reopen |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}` | delete (admin) |
|
|
| POST | `/repos/{owner}/{repo}/issues/{index}/pin` | pin |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}/pin` | unpin |
|
|
| PATCH | `/repos/{owner}/{repo}/issues/{index}/pin/{position}` | move pin |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}/blocks` | list blocked issues |
|
|
| POST | `/repos/{owner}/{repo}/issues/{index}/blocks` | add blocking |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}/blocks` | remove blocking |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}/dependencies` | list blockers |
|
|
| POST | `/repos/{owner}/{repo}/issues/{index}/dependencies` | add dependency |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}/dependencies` | remove dependency |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}/comments` | list comments |
|
|
| POST | `/repos/{owner}/{repo}/issues/{index}/comments` | add comment |
|
|
| GET | `/repos/{owner}/{repo}/issues/comments/{id}` | get comment |
|
|
| PATCH | `/repos/{owner}/{repo}/issues/comments/{id}` | edit comment |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/comments/{id}` | delete comment |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}/timeline` | comments + events |
|
|
| GET | `/repos/{owner}/{repo}/issues/{index}/labels` | get labels |
|
|
| POST | `/repos/{owner}/{repo}/issues/{index}/labels` | add labels |
|
|
| PUT | `/repos/{owner}/{repo}/issues/{index}/labels` | replace labels |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}/labels` | clear all labels |
|
|
| DELETE | `/repos/{owner}/{repo}/issues/{index}/labels/{id}` | remove one label |
|
|
| GET | `/repos/issues/search` | cross-repo search |
|
|
| GET | `/repos/{owner}/{repo}/issues/pinned` | list pinned |
|
|
|
|
### Pull Requests
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/repos/{owner}/{repo}/pulls` | list |
|
|
| POST | `/repos/{owner}/{repo}/pulls` | create |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}` | get |
|
|
| PATCH | `/repos/{owner}/{repo}/pulls/{index}` | edit |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}.diff` | get diff |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}.patch` | get patch |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/commits` | get commits |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/files` | get changed files |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/merge` | check if merged |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/merge` | merge |
|
|
| DELETE | `/repos/{owner}/{repo}/pulls/{index}/merge` | cancel auto-merge |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/update` | sync base into head |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{base}/{head}` | get by branches |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/reviews` | list reviews |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/reviews` | create review |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` | get review |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` | submit pending review |
|
|
| DELETE | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}` | delete review |
|
|
| GET | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments` | get review comments |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals` | dismiss review |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismissals` | undismiss review |
|
|
| POST | `/repos/{owner}/{repo}/pulls/{index}/requested_reviewers` | request reviewers |
|
|
| DELETE | `/repos/{owner}/{repo}/pulls/{index}/requested_reviewers` | cancel review request |
|
|
| GET | `/repos/{owner}/{repo}/pulls/pinned` | list pinned PRs |
|
|
|
|
### Labels
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/repos/{owner}/{repo}/labels` | list |
|
|
| POST | `/repos/{owner}/{repo}/labels` | create |
|
|
| GET | `/repos/{owner}/{repo}/labels/{id}` | get |
|
|
| PATCH | `/repos/{owner}/{repo}/labels/{id}` | update |
|
|
| DELETE | `/repos/{owner}/{repo}/labels/{id}` | delete |
|
|
|
|
### Milestones
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/repos/{owner}/{repo}/milestones` | list (`state`, `name`, `page`, `limit`) |
|
|
| POST | `/repos/{owner}/{repo}/milestones` | create |
|
|
| GET | `/repos/{owner}/{repo}/milestones/{id}` | get |
|
|
| PATCH | `/repos/{owner}/{repo}/milestones/{id}` | update |
|
|
| DELETE | `/repos/{owner}/{repo}/milestones/{id}` | delete |
|
|
|
|
### Commit Statuses
|
|
| Method | Path | Notes |
|
|
|---|---|---|
|
|
| GET | `/repos/{owner}/{repo}/commits/{ref}/status` | combined status |
|
|
| GET | `/repos/{owner}/{repo}/commits/{ref}/statuses` | list by ref |
|
|
| GET | `/repos/{owner}/{repo}/statuses/{sha}` | list by SHA |
|
|
| POST | `/repos/{owner}/{repo}/statuses/{sha}` | create status |
|