# Forgejo API — Repository Topics

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

---

## Endpoints Overview

| Method | Endpoint | Description |
|--------|----------|-------------|
| `GET` | `/repos/{owner}/{repo}/topics` | List all topics on a repo |
| `PUT` | `/repos/{owner}/{repo}/topics` | Replace all topics |
| `PUT` | `/repos/{owner}/{repo}/topics/{topic}` | Add a single topic |
| `DELETE` | `/repos/{owner}/{repo}/topics/{topic}` | Remove a single topic |
| `GET` | `/topics/search` | Search topics across the instance |

---

## Topic Constraints

- Topics must be lowercase
- Topics can contain letters, numbers, and hyphens
- Topics cannot start with a hyphen
- Maximum length: 35 characters
- Maximum topics per repo: 25 (server-configurable)

---

## List Repository Topics

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

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Repository owner (user or org) |
| `repo` | string | Repository name |

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `page` | integer | Page number (1-based) |
| `limit` | integer | Page size |

```bash
# List all topics on a repo
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"
```

**Response:** `200 OK`

```json
{
  "topics": [
    "golang",
    "web-framework",
    "rest-api"
  ]
}
```

**Response Fields:**

| Field | Type | Description |
|-------|------|-------------|
| `topics` | array[string] | List of topic names |

---

## Replace All Topics

Replaces the entire set of topics on a repository. Any existing topics not in the new list are removed.

```
PUT /repos/{owner}/{repo}/topics
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Repository owner |
| `repo` | string | Repository name |

**Request Body (RepoTopicOptions):**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `topics` | array[string] | **Yes** | Complete list of topics to set |

```bash
# Set topics on a repo (replaces all existing)
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "topics": ["golang", "web-framework", "rest-api", "microservice"]
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"

# Set a single topic (removes all others)
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "topics": ["documentation"]
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"

# Remove all topics
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "topics": []
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"

# Set topics for a Python data science project
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "topics": [
      "python",
      "data-science",
      "machine-learning",
      "pandas",
      "jupyter",
      "visualization"
    ]
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"
```

**Response:** `204 No Content`

---

## Add a Single Topic

Adds a topic without affecting other existing topics.

```
PUT /repos/{owner}/{repo}/topics/{topic}
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Repository owner |
| `repo` | string | Repository name |
| `topic` | string | Topic name to add (lowercase, letters/numbers/hyphens) |

```bash
# Add "docker" topic
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"

# Add "kubernetes" topic
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"

# Add "hacktoberfest" topic
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"

# Add topic with hyphens
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"
```

**Response:** `204 No Content`

**Note:** If the topic already exists on the repo, this is a no-op and still returns `204`.

---

## Remove a Single Topic

Removes a topic without affecting other existing topics.

```
DELETE /repos/{owner}/{repo}/topics/{topic}
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Repository owner |
| `repo` | string | Repository name |
| `topic` | string | Topic name to remove |

```bash
# Remove "deprecated" topic
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"

# Remove "hacktoberfest" topic
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics/{topic}"
```

**Response:** `204 No Content`

**Note:** If the topic does not exist on the repo, this still returns `204`.

---

## Search Topics

Search topics across the entire Forgejo instance.

```
GET /topics/search
```

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `q` | string | Search query keyword |
| `page` | integer | Page number (1-based) |
| `limit` | integer | Page size |

```bash
# Search for topics matching "go"
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/topics/search?q=go"

# Search for topics matching "web"
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/topics/search?q=web&limit=20"

# Search for topics matching "python"
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/topics/search?q=python&page=1&limit=10"

# Search for topics matching "ci-cd"
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/topics/search?q=ci-cd"
```

**Response:** `200 OK`

```json
{
  "topics": [
    {
      "id": 1,
      "name": "golang",
      "repo_count": 15,
      "created": "2024-01-15T10:00:00Z",
      "updated": "2024-06-01T12:30:00Z"
    },
    {
      "id": 2,
      "name": "go-library",
      "repo_count": 5,
      "created": "2024-02-20T08:15:00Z",
      "updated": "2024-05-15T09:00:00Z"
    }
  ]
}
```

**Topic Object Fields:**

| Field | Type | Description |
|-------|------|-------------|
| `id` | integer | Topic ID |
| `name` | string | Topic name |
| `repo_count` | integer | Number of repos using this topic |
| `created` | string | ISO 8601 creation timestamp |
| `updated` | string | ISO 8601 last update timestamp |

---

## Common Patterns

### Copy Topics from One Repo to Another

```bash
# Get topics from source repo
TOPICS=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics" | jq -c '.topics')

# Apply to destination repo
curl -s -X PUT \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d "{\"topics\": ${TOPICS}}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/topics"
```

### Add Topics to Multiple Repos

```bash
# Add "maintained" topic to several repos
for repo in {repo1} {repo2} {repo3}; do
  curl -s -X PUT \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/{owner}/${repo}/topics/{topic}"
  echo "Added 'maintained' to ${repo}"
done
```

### Remove a Topic from All Repos

```bash
# Find repos with "deprecated" topic and remove it
REPOS=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/search?q=deprecated&topic=true" | jq -r '.data[].full_name')

for repo in $REPOS; do
  curl -s -X DELETE \
    -H "Authorization: token ${FORGEJO_PAT}" \
    "${FORGEJO_URL}/api/v1/repos/${repo}/topics/{topic}"
  echo "Removed 'deprecated' from ${repo}"
done
```

---

## Error Responses

| Code | Description |
|------|-------------|
| `403 Forbidden` | Insufficient permissions (write access required) |
| `404 Not Found` | Repository not found |
| `422 Unprocessable Entity` | Invalid topic name (wrong format or exceeds limits) |
