# Forgejo Issues API

Base URL: `https://git.cleverthis.com/api/v1`

All endpoints require authentication via `Authorization: token ${FORGEJO_PAT}` header.

---

## List Repository Issues

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

Returns issues (and optionally pull requests) for a repository.

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `state` | string | query | Filter by state: `open`, `closed`, `all`. Default: `open` |
| `labels` | string | query | Comma-separated list of label names or IDs |
| `milestones` | string | query | Comma-separated list of milestone names or IDs |
| `q` | string | query | Search string (searches title and body) |
| `type` | string | query | Filter by type: `issues`, `pulls`. Default: returns both |
| `page` | integer | query | Page number (1-based). Default: `1` |
| `limit` | integer | query | Page size. Default: server default (usually 20, max 50) |
| `sort` | string | query | Sort field. Options: `oldest`, `recentupdate`, `leastupdate`, `mostcomment`, `leastcomment`, `priority`. Default: newest first |
| `since` | string | query | Only show issues updated after this date (ISO 8601 format, e.g. `2024-01-01T00:00:00Z`) |
| `before` | string | query | Only show issues updated before this date (ISO 8601 format) |
| `created_by` | string | query | Filter by creator username |
| `assigned_by` | string | query | Filter by assignee username |
| `mentioned_by` | string | query | Filter by mentioned username |

### Response

Returns `200` with an array of Issue objects. Pagination info is in response headers:
- `X-Total-Count` — total number of matching issues
- `Link` — standard pagination links (first, prev, next, last)

### Examples

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

# List closed issues with pagination
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?state=closed&page=2&limit=10"

# Search issues by keyword
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?q=login+bug&state=open"

# Filter by labels (comma-separated label names)
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?labels=bug,critical"

# Filter by milestone
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?milestones={milestone_name}"

# Issues updated since a date
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?since=2024-06-01T00:00:00Z"

# Issues created by a specific user, sorted by most comments
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?created_by={username}&sort=mostcomment"

# Issues assigned to a user
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?assigned_by={username}"

# Combine multiple filters
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues?state=open&labels=bug&milestones=v2.0&sort=priority&limit=50"
```

---

## Create an Issue

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

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |

### Request Body — `CreateIssueOption`

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | **Yes** | Issue title |
| `body` | string | No | Issue body (Markdown supported) |
| `assignees` | array of strings | No | Usernames to assign |
| `milestone` | integer | No | Milestone ID to associate |
| `labels` | array of integers | No | Label IDs to apply |
| `closed` | boolean | No | If `true`, create the issue already closed |
| `due_date` | string | No | Due date in ISO 8601 format (e.g. `2025-12-31T00:00:00Z`) |
| `ref` | string | No | Branch reference for this issue |

### Response

Returns `201` with the created Issue object.

### Examples

```bash
# Create a simple issue
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Fix login page redirect"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues"

# Create issue with full details
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Implement OAuth2 support",
    "body": "## Description\n\nWe need to add OAuth2 authentication.\n\n## Tasks\n- [ ] Add OAuth2 provider config\n- [ ] Implement callback handler\n- [ ] Update login page",
    "assignees": ["{assignee1}", "{assignee2}"],
    "milestone": 3,
    "labels": [1, 5, 12],
    "due_date": "2025-03-01T00:00:00Z",
    "ref": "feature/oauth2"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues"

# Create an already-closed issue (useful for record-keeping)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Legacy: migrated from old tracker #4521",
    "body": "This issue was migrated from the old tracker and is already resolved.",
    "closed": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues"
```

---

## Get a Single Issue

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

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number (the `#` number shown in the UI) |

### Response

Returns `200` with an Issue object. Returns `404` if not found.

### Key fields in the Issue response object

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Internal database ID |
| `number` | integer | Issue index (same as `index` param) |
| `title` | string | Issue title |
| `body` | string | Issue body (Markdown) |
| `state` | string | `open` or `closed` |
| `labels` | array | Label objects |
| `milestone` | object | Milestone object (or null) |
| `assignees` | array | Assignee user objects |
| `user` | object | Creator user object |
| `comments` | integer | Number of comments |
| `created_at` | string | ISO 8601 creation timestamp |
| `updated_at` | string | ISO 8601 last update timestamp |
| `closed_at` | string | ISO 8601 close timestamp (or null) |
| `due_date` | string | ISO 8601 due date (or null) |
| `pull_request` | object | Present if this is a PR (null for issues) |
| `ref` | string | Branch reference |
| `pin_order` | integer | Pin position (0 if not pinned) |
| `is_locked` | boolean | Whether the issue is locked |
| `original_author` | string | Original author name (for migrated issues) |
| `original_author_id` | integer | Original author ID (for migrated issues) |

### Examples

```bash
# Get issue #42
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Get issue and extract specific fields with jq
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}" | jq '{title, state, assignees: [.assignees[].login], labels: [.labels[].name]}'
```

---

## Edit an Issue

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

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

### Request Body — `EditIssueOption`

All fields are optional. Only include fields you want to change.

| Field | Type | Description |
|-------|------|-------------|
| `title` | string | New title |
| `body` | string | New body (Markdown) |
| `assignees` | array of strings | New assignee usernames (replaces all current assignees) |
| `milestone` | integer | New milestone ID. Set to `0` to remove milestone |
| `state` | string | `"open"` or `"closed"` |
| `due_date` | string | New due date (ISO 8601) |
| `unset_due_date` | boolean | If `true`, removes the due date |
| `ref` | string | New branch reference |
| `updated_at` | string | Override the updated timestamp (ISO 8601). Useful for migrations |

### Response

Returns `201` with the updated Issue object.

### Examples

```bash
# Update issue title
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Fix login page redirect (updated)"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

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

# Reopen an issue
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "open"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Update body, assignees, and milestone
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "body": "## Updated Description\n\nThis is the revised description.",
    "assignees": ["{assignee1}", "{assignee2}"],
    "milestone": 5
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Set a due date
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "due_date": "2025-06-15T00:00:00Z"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Remove a due date
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "unset_due_date": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Remove milestone (set to 0)
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "milestone": 0
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"

# Change branch reference
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "ref": "{branch}"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"
```

---

## Delete an Issue

```
DELETE /repos/{owner}/{repo}/issues/{index}
```

**Note:** This permanently deletes the issue. Requires admin permissions on the repository. In most cases, prefer closing an issue instead.

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index number |

### Response

Returns `204` (No Content) on success.

### Example

```bash
# Delete issue #42 (admin only, irreversible)
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}"
```

---

## Pinned Issues

### List Pinned Issues

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

Returns issues that are pinned in the repository, ordered by their pin position.

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

### Pin an Issue

```
POST /repos/{owner}/{repo}/issues/{index}/pin
```

Pins the issue to the repository. Requires write access.

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

Returns `204` on success.

### Unpin an Issue

```
DELETE /repos/{owner}/{repo}/issues/{index}/pin
```

Removes the pin from an issue.

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

Returns `204` on success.

### Move a Pinned Issue

```
PATCH /repos/{owner}/{repo}/issues/{index}/pin/{position}
```

Moves a pinned issue to a new position.

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `index` | integer | path | **Required.** Issue index |
| `position` | integer | path | **Required.** New position (1-based) |

```bash
# Move pinned issue #42 to position 1 (top)
curl -s -X PATCH \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/pin/{position}"
```

Returns `204` on success.

---

## Issue Subscriptions

### List Subscribers

```
GET /repos/{owner}/{repo}/issues/{index}/subscriptions
```

Returns users subscribed to the issue.

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `page` | integer | query | Page number. Default: `1` |
| `limit` | integer | query | Page size |

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

### Check if Current User is Subscribed

```
GET /repos/{owner}/{repo}/issues/{index}/subscriptions/check
```

Returns a WatchInfo object with `subscribed` (boolean), `ignored` (boolean), `reason`, `created_at`, and `url`.

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

### Subscribe a User

```
PUT /repos/{owner}/{repo}/issues/{index}/subscriptions/{user}
```

Subscribes a user to the issue. You can subscribe yourself or others (with appropriate permissions).

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `user` | string | path | **Required.** Username to subscribe |

```bash
# Subscribe user "{username}" to issue #42
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{username}"
```

Returns `200` on success, `304` if already subscribed.

### Unsubscribe a User

```
DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions/{user}
```

```bash
# Unsubscribe user "{username}" from issue #42
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{username}"
```

Returns `200` on success, `304` if not subscribed.

---

## Issue Timeline

```
GET /repos/{owner}/{repo}/issues/{index}/timeline
```

Returns a chronological list of all events on an issue (comments, label changes, assignments, state changes, references, etc.).

### Parameters

| Parameter | Type | In | Description |
|-----------|------|------|-------------|
| `owner` | string | path | **Required.** Repository owner |
| `repo` | string | path | **Required.** Repository name |
| `index` | integer | path | **Required.** Issue index |
| `since` | string | query | Only show events after this date (ISO 8601) |
| `before` | string | query | Only show events before this date (ISO 8601) |
| `page` | integer | query | Page number. Default: `1` |
| `limit` | integer | query | Page size |

### Timeline Event Types

The `type` field in each event indicates what happened:

| Type | Description |
|------|-------------|
| `comment` | A comment was posted |
| `reopen` | Issue was reopened |
| `close` | Issue was closed |
| `label` | Label was added or removed |
| `milestone` | Milestone was changed |
| `assignees` | Assignees were changed |
| `change_title` | Title was changed |
| `delete_branch` | Associated branch was deleted |
| `commit_ref` | A commit referenced this issue |
| `pull_ref` | A pull request referenced this issue |
| `comment_ref` | A comment referenced this issue |
| `issue_ref` | Another issue referenced this one |
| `change_ref` | Branch reference was changed |
| `head_ref` | Head reference was changed |
| `due_date` | Due date was added, changed, or removed |
| `pin` | Issue was pinned |
| `unpin` | Issue was unpinned |
| `lock` | Issue was locked |
| `unlock` | Issue was unlocked |
| `project_board` | Issue was added/moved in project board |
| `added_deadline` | Deadline added |
| `modified_deadline` | Deadline changed |
| `removed_deadline` | Deadline removed |
| `review_request` | Review was requested |
| `merge` | PR was merged |
| `code` | Code change event |

### Examples

```bash
# Get full timeline for issue #42
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/timeline"

# Get timeline events since a specific date
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/timeline?since=2024-06-01T00:00:00Z"

# Get timeline with pagination
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/timeline?page=1&limit=50"

# Extract just state-change events with jq
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/issues/{index}/timeline" \
  | jq '[.[] | select(.type == "close" or .type == "reopen")]'
```
