# Forgejo API — Repository Transfers, Mirrors & Conversion

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

---

## Endpoints Overview

| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/repos/{owner}/{repo}/transfer` | Transfer repository to new owner |
| `POST` | `/repos/{owner}/{repo}/transfer/accept` | Accept pending transfer |
| `POST` | `/repos/{owner}/{repo}/transfer/reject` | Reject pending transfer |
| `POST` | `/repos/{owner}/{repo}/mirror-sync` | Trigger mirror sync (pull mirror) |
| `GET` | `/repos/{owner}/{repo}/push_mirrors` | List push mirrors |
| `POST` | `/repos/{owner}/{repo}/push_mirrors` | Add push mirror |
| `DELETE` | `/repos/{owner}/{repo}/push_mirrors/{name}` | Delete push mirror |
| `POST` | `/repos/{owner}/{repo}/push_mirrors-sync` | Sync all push mirrors |
| `POST` | `/repos/{owner}/{repo}/convert` | Convert fork to standalone repo |

---

## Transfer Endpoints

### Transfer Repository

Initiates a repository transfer to a new owner (user or organization). The recipient must accept the transfer unless the authenticated user is an admin of the target organization.

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

**Path Parameters:**

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

**Request Body (TransferRepoOption):**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `new_owner` | string | **Yes** | Username or organization name to transfer to |
| `team_ids` | array[integer] | No | Team IDs to grant access (org transfers only) |

```bash
# Transfer repo to another user
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "new_owner": "another-user"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer"

# Transfer repo to an organization
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "new_owner": "{new_owner}"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer"

# Transfer to org and grant specific teams access
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "new_owner": "{new_owner}",
    "team_ids": [1, 5, 12]
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer"
```

**Response:** `202 Accepted` — `{Repository}` object (transfer is pending acceptance)

**Notes:**
- If the authenticated user is admin of the target org, the transfer completes immediately
- Otherwise, the target user/org admin must accept the transfer
- The repo remains accessible at its original URL until the transfer is accepted
- Existing webhooks, collaborators, and teams are preserved

---

### Accept Transfer

Accept a pending repository transfer.

```
POST /repos/{owner}/{repo}/transfer/accept
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Current repository owner (original) |
| `repo` | string | Repository name |

```bash
# Accept a pending transfer
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer/accept"
```

**Response:** `202 Accepted` — `{Repository}` object with updated owner

**Notes:**
- Must be authenticated as the target user or admin of the target organization
- After acceptance, the repo URL changes to reflect the new owner
- Redirects from the old URL are set up automatically

---

### Reject Transfer

Reject a pending repository transfer.

```
POST /repos/{owner}/{repo}/transfer/reject
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Current repository owner (original) |
| `repo` | string | Repository name |

```bash
# Reject a pending transfer
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer/reject"
```

**Response:** `200 OK` — `{Repository}` object (transfer cancelled, repo stays with original owner)

---

## Mirror Endpoints

### Trigger Mirror Sync (Pull Mirror)

Forces an immediate sync for a pull mirror repository.

```
POST /repos/{owner}/{repo}/mirror-sync
```

**Path Parameters:**

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

```bash
# Trigger mirror sync
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/mirror-sync"
```

**Response:** `200 OK`

**Notes:**
- Only works on mirror repositories (created via migration with `mirror: true`)
- Syncs all branches and tags from the upstream source
- Mirror interval can be configured via `PATCH /repos/{owner}/{repo}` with `mirror_interval`

---

### List Push Mirrors

List all push mirror targets configured for a repository.

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

**Path Parameters:**

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

**Query Parameters:**

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

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

**Response:** `200 OK` — Array of `{PushMirror}` objects

```json
[
  {
    "repo_name": "{repo}",
    "remote_name": "{mirror_name}",
    "remote_address": "https://github.com/{owner}/{repo}.git",
    "sync_on_commit": true,
    "interval": "8h0m0s",
    "created_unix": 1700000000,
    "last_update": "2024-06-01T12:00:00Z",
    "last_error": ""
  }
]
```

**PushMirror Object Fields:**

| Field | Type | Description |
|-------|------|-------------|
| `repo_name` | string | Repository name |
| `remote_name` | string | Remote name identifier |
| `remote_address` | string | Target URL |
| `sync_on_commit` | boolean | Whether to sync on every push |
| `interval` | string | Sync interval (Go duration format) |
| `created_unix` | integer | Creation timestamp (Unix) |
| `last_update` | string | Last sync time (ISO 8601) |
| `last_error` | string | Last error message (empty if no error) |

---

### Add Push Mirror

Configure a new push mirror target for a repository.

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

**Path Parameters:**

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

**Request Body (CreatePushMirrorOption):**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `remote_address` | string | **Yes** | Target repository URL to push to |
| `remote_username` | string | No | Username for authentication |
| `remote_password` | string | No | Password or token for authentication |
| `interval` | string | No | Sync interval (e.g., `"8h"`, `"1d"`) |
| `sync_on_commit` | boolean | No | Sync immediately on each push (default: `false`) |

```bash
# Add push mirror to GitHub
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "remote_address": "https://github.com/{owner}/{repo}.git",
    "remote_username": "{remote_user}",
    "remote_password": "ghp_xxxxxxxxxxxx",
    "interval": "8h",
    "sync_on_commit": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors"

# Add push mirror to GitLab
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "remote_address": "https://gitlab.com/{owner}/{repo}.git",
    "remote_username": "oauth2",
    "remote_password": "glpat-xxxxxxxxxxxx",
    "interval": "1h",
    "sync_on_commit": false
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors"

# Add push mirror to another Forgejo/Gitea instance
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "remote_address": "https://{remote_host}/{owner}/{repo}.git",
    "remote_username": "{remote_user}",
    "remote_password": "other-instance-token",
    "interval": "24h",
    "sync_on_commit": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors"

# Add push mirror with sync on commit only (no interval)
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "remote_address": "https://github.com/{backup_owner}/{repo}.git",
    "remote_username": "{remote_user}",
    "remote_password": "ghp_xxxxxxxxxxxx",
    "sync_on_commit": true
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors"
```

**Response:** `200 OK` — `{PushMirror}` object

---

### Delete Push Mirror

```
DELETE /repos/{owner}/{repo}/push_mirrors/{name}
```

**Path Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `owner` | string | Repository owner |
| `repo` | string | Repository name |
| `name` | string | Remote name of the push mirror (e.g., `"{mirror_name}"`) |

```bash
# Delete a push mirror
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors/{mirror_name}"

# Delete by specific remote name
curl -s -X DELETE \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors/{mirror_name}"
```

**Response:** `204 No Content`

---

### Sync All Push Mirrors

Trigger an immediate sync of all push mirror targets.

```
POST /repos/{owner}/{repo}/push_mirrors-sync
```

**Path Parameters:**

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

```bash
# Sync all push mirrors now
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors-sync"
```

**Response:** `200 OK`

---

## Conversion Endpoints

### Convert Fork to Standalone Repository

Converts a forked repository into a standalone (non-fork) repository. This removes the link to the parent/upstream repository.

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

**Path Parameters:**

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

```bash
# Convert a fork to a standalone repo
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/convert"
```

**Response:** `200 OK` — `{Repository}` object with `fork: false` and `parent: null`

**Notes:**
- This is irreversible — once converted, the fork relationship cannot be restored
- The repository keeps all its content, branches, issues, etc.
- The `fork` field becomes `false` and `parent` becomes `null`
- Fork sync endpoints will no longer work for this repo
- Requires admin permission on the repository

---

## Common Patterns

### Transfer Repo to Org with Team Access

```bash
# 1. Get team IDs for the org
TEAMS=$(curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/orgs/{org}/teams" | jq '[.[].id]')

# 2. Transfer with all teams
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d "{
    \"new_owner\": \"{new_owner}\",
    \"team_ids\": ${TEAMS}
  }" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/transfer"
```

### Set Up Bidirectional Mirroring

```bash
# Set up push mirror from Forgejo to GitHub
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  -H "Content-Type: application/json" \
  -d '{
    "remote_address": "https://github.com/{owner}/{repo}.git",
    "remote_username": "{remote_user}",
    "remote_password": "ghp_xxxxxxxxxxxx",
    "sync_on_commit": true,
    "interval": "1h"
  }' \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors"
```

### Check Push Mirror Health

```bash
# List push mirrors and check for errors
curl -s \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors" | \
  jq '.[] | {remote_name, remote_address, last_update, last_error}'
```

### Force Sync All Mirrors

```bash
# Sync pull mirror
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/mirror-sync"

# Sync all push mirrors
curl -s -X POST \
  -H "Authorization: token ${FORGEJO_PAT}" \
  "${FORGEJO_URL}/api/v1/repos/{owner}/{repo}/push_mirrors-sync"

echo "All mirrors synced."
```

---

## Error Responses

| Code | Description |
|------|-------------|
| `403 Forbidden` | Insufficient permissions (admin required for transfers and mirror management) |
| `404 Not Found` | Repository not found, or repo is not a mirror/fork |
| `422 Unprocessable Entity` | Invalid parameters (e.g., transfer to self, invalid remote URL) |
| `409 Conflict` | Repository with that name already exists at target owner |
