feat: add API contracts
Document all Gitea REST API v1 and GitHub REST API endpoints used across phases 1-9. Each endpoint includes: method, path, request/response schemas, status codes, and which script uses it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
474
contracts/gitea-api.md
Normal file
474
contracts/gitea-api.md
Normal file
@@ -0,0 +1,474 @@
|
||||
# API Contracts — Gitea Migration Toolkit
|
||||
|
||||
All API calls used by the migration scripts. Every `gitea_api`, `gitea_backup_api`, and `github_api` call in the codebase MUST have a corresponding entry here.
|
||||
|
||||
---
|
||||
|
||||
## Gitea REST API v1
|
||||
|
||||
Base URL: `${GITEA_INTERNAL_URL}/api/v1` (primary) or `${GITEA_BACKUP_INTERNAL_URL}/api/v1` (backup)
|
||||
Auth: `Authorization: token ${GITEA_ADMIN_TOKEN}` (or `GITEA_BACKUP_ADMIN_TOKEN`)
|
||||
|
||||
---
|
||||
|
||||
### POST /users/{username}/tokens
|
||||
|
||||
**Used in**: Phase 1 (primary), Phase 2 (backup)
|
||||
**Purpose**: Generate API token for admin user
|
||||
**Auth**: Basic auth (`-u username:password`) — token doesn't exist yet
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"scopes": ["all"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (201):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "migration-token",
|
||||
"sha1": "abc123...",
|
||||
"token_last_eight": "abc12345"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**: `sha1` field contains the full token value — save this immediately, it's only returned once.
|
||||
|
||||
---
|
||||
|
||||
### GET /user
|
||||
|
||||
**Used in**: Preflight (validate token), Phase 1 post-check
|
||||
**Purpose**: Verify the current authenticated user
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"login": "string",
|
||||
"email": "string",
|
||||
"is_admin": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /orgs
|
||||
|
||||
**Used in**: Phase 1
|
||||
**Purpose**: Create organization
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"username": "string",
|
||||
"full_name": "string",
|
||||
"description": "string",
|
||||
"visibility": "public"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (201):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"full_name": "string",
|
||||
"visibility": "public"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /orgs/{org}
|
||||
|
||||
**Used in**: Phase 1 (idempotency check), Phase 1 post-check
|
||||
**Purpose**: Check if organization exists
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"full_name": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**404**: Organization does not exist
|
||||
|
||||
---
|
||||
|
||||
### POST /repos/migrate
|
||||
|
||||
**Used in**: Phase 4 (import from GitHub), Phase 4 (Fedora pull mirrors)
|
||||
**Purpose**: Migrate/mirror a repository from an external source
|
||||
|
||||
**Request (GitHub import — primary)**:
|
||||
```json
|
||||
{
|
||||
"clone_addr": "https://github.com/{owner}/{repo}.git",
|
||||
"auth_token": "string (GitHub PAT)",
|
||||
"repo_owner": "string (Gitea org name)",
|
||||
"repo_name": "string",
|
||||
"service": "github",
|
||||
"mirror": false,
|
||||
"issues": true,
|
||||
"labels": true,
|
||||
"milestones": false,
|
||||
"wiki": false
|
||||
}
|
||||
```
|
||||
|
||||
**Request (Fedora pull mirror)**:
|
||||
```json
|
||||
{
|
||||
"clone_addr": "http://{UNRAID_IP}:{PORT}/{org}/{repo}.git",
|
||||
"auth_username": "string",
|
||||
"auth_password": "string",
|
||||
"repo_owner": "string (admin username on Fedora)",
|
||||
"repo_name": "string",
|
||||
"mirror": true,
|
||||
"mirror_interval": "8h"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (201):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"full_name": "org/repo",
|
||||
"empty": false,
|
||||
"mirror": false,
|
||||
"default_branch": "main"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**: Migration is async — poll `GET /repos/{owner}/{repo}` until `empty` is `false`.
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}
|
||||
|
||||
**Used in**: Phase 4 (idempotency + polling), Phase 4 post-check, Phase 5 post-check
|
||||
**Purpose**: Get repository details
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"full_name": "org/repo",
|
||||
"empty": false,
|
||||
"mirror": false,
|
||||
"default_branch": "main",
|
||||
"archived": false,
|
||||
"description": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**404**: Repository does not exist
|
||||
|
||||
---
|
||||
|
||||
### DELETE /repos/{owner}/{repo}
|
||||
|
||||
**Used in**: Phase 4 teardown
|
||||
**Purpose**: Delete a repository
|
||||
|
||||
**Request**: No body
|
||||
**Response**: 204 No Content
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/commits
|
||||
|
||||
**Used in**: Phase 4 post-check
|
||||
**Purpose**: Verify repo has commits
|
||||
|
||||
**Query params**: `?limit=1`
|
||||
|
||||
**Response** (200): Array of commit objects (at least 1 if repo has content)
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/contents/{filepath}
|
||||
|
||||
**Used in**: Phase 5 post-check (check `.gitea/workflows/` exists), Phase 9 (check security workflow exists)
|
||||
**Purpose**: Get file or directory contents
|
||||
|
||||
**Response** (200): File/directory metadata
|
||||
**404**: Path does not exist
|
||||
|
||||
---
|
||||
|
||||
### GET /admin/runners/registration-token
|
||||
|
||||
**Used in**: Phase 3
|
||||
**Purpose**: Get registration token for new runners
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Notes**: Requires admin-level API token.
|
||||
|
||||
---
|
||||
|
||||
### GET /admin/runners
|
||||
|
||||
**Used in**: Phase 3 post-check
|
||||
**Purpose**: List all registered runners
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "string",
|
||||
"status": "online",
|
||||
"labels": ["linux"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /repos/{owner}/{repo}/push_mirrors
|
||||
|
||||
**Used in**: Phase 6
|
||||
**Purpose**: Create a push mirror to GitHub
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"remote_address": "https://github.com/{owner}/{repo}.git",
|
||||
"remote_username": "string",
|
||||
"remote_password": "string (GitHub PAT)",
|
||||
"interval": "8h",
|
||||
"sync_on_commit": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (201):
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"remote_name": "string",
|
||||
"remote_address": "string",
|
||||
"interval": "8h",
|
||||
"sync_on_commit": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/push_mirrors
|
||||
|
||||
**Used in**: Phase 6 (idempotency check), Phase 6 post-check
|
||||
**Purpose**: List push mirrors for a repo
|
||||
|
||||
**Response** (200): Array of push mirror objects
|
||||
|
||||
---
|
||||
|
||||
### DELETE /repos/{owner}/{repo}/push_mirrors/{id}
|
||||
|
||||
**Used in**: Phase 6 teardown
|
||||
**Purpose**: Remove a push mirror
|
||||
|
||||
**Response**: 204 No Content
|
||||
|
||||
---
|
||||
|
||||
### POST /repos/{owner}/{repo}/push_mirrors-sync
|
||||
|
||||
**Used in**: Phase 6 post-check
|
||||
**Purpose**: Trigger immediate push mirror sync
|
||||
|
||||
**Request**: No body
|
||||
**Response**: 200 OK
|
||||
|
||||
---
|
||||
|
||||
### POST /repos/{owner}/{repo}/branch_protections
|
||||
|
||||
**Used in**: Phase 7, Phase 9 (update with status checks)
|
||||
**Purpose**: Create branch protection rule
|
||||
|
||||
**Request**:
|
||||
```json
|
||||
{
|
||||
"branch_name": "main",
|
||||
"enable_push": false,
|
||||
"enable_push_whitelist": false,
|
||||
"require_signed_commits": false,
|
||||
"enable_status_check": true,
|
||||
"status_check_contexts": [],
|
||||
"enable_approvals_whitelist": false,
|
||||
"required_approvals": 1
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (201): Branch protection object
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/branch_protections
|
||||
|
||||
**Used in**: Phase 7 (idempotency), Phase 7 post-check
|
||||
**Purpose**: List all branch protection rules
|
||||
|
||||
**Response** (200): Array of branch protection objects
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/branch_protections/{name}
|
||||
|
||||
**Used in**: Phase 7 (check specific branch)
|
||||
**Purpose**: Get a specific branch protection rule
|
||||
|
||||
**Response** (200): Branch protection object
|
||||
**404**: Rule does not exist
|
||||
|
||||
---
|
||||
|
||||
### PATCH /repos/{owner}/{repo}/branch_protections/{name}
|
||||
|
||||
**Used in**: Phase 9 (add status check contexts)
|
||||
**Purpose**: Update branch protection rule
|
||||
|
||||
**Request** (partial update):
|
||||
```json
|
||||
{
|
||||
"enable_status_check": true,
|
||||
"status_check_contexts": ["semgrep", "trivy", "gitleaks"]
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200): Updated branch protection object
|
||||
|
||||
---
|
||||
|
||||
### DELETE /repos/{owner}/{repo}/branch_protections/{name}
|
||||
|
||||
**Used in**: Phase 7 teardown, Phase 9 teardown
|
||||
**Purpose**: Delete branch protection rule
|
||||
|
||||
**Response**: 204 No Content
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}/actions/workflows
|
||||
|
||||
**Used in**: Phase 5 post-check
|
||||
**Purpose**: List workflows in repo
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"total_count": 1,
|
||||
"workflows": [
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"path": ".gitea/workflows/ci.yml",
|
||||
"state": "active"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /settings/api
|
||||
|
||||
**Used in**: Phase 1 post-check (verify Actions enabled)
|
||||
**Purpose**: Get instance API settings
|
||||
|
||||
**Response** (200): Settings object with `has_actions` field
|
||||
|
||||
---
|
||||
|
||||
## GitHub REST API
|
||||
|
||||
Base URL: `https://api.github.com`
|
||||
Auth: `Authorization: token ${GITHUB_TOKEN}`
|
||||
|
||||
---
|
||||
|
||||
### GET /user
|
||||
|
||||
**Used in**: Preflight
|
||||
**Purpose**: Validate GitHub personal access token
|
||||
|
||||
**Request**: No body
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"login": "string",
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
**401**: Token invalid or expired
|
||||
|
||||
---
|
||||
|
||||
### GET /repos/{owner}/{repo}
|
||||
|
||||
**Used in**: Preflight (verify repos exist), Phase 8 (check archive status)
|
||||
**Purpose**: Get repository details
|
||||
|
||||
**Response** (200):
|
||||
```json
|
||||
{
|
||||
"name": "string",
|
||||
"full_name": "owner/repo",
|
||||
"archived": false,
|
||||
"description": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**404**: Repository does not exist
|
||||
|
||||
---
|
||||
|
||||
### PATCH /repos/{owner}/{repo}
|
||||
|
||||
**Used in**: Phase 8 (archive repos + update description), Phase 8 teardown (un-archive)
|
||||
**Purpose**: Update repository settings
|
||||
|
||||
**Request (archive)**:
|
||||
```json
|
||||
{
|
||||
"archived": true,
|
||||
"description": "[MOVED] Now at https://git.domain.com/org/repo — was: original description"
|
||||
}
|
||||
```
|
||||
|
||||
**Request (un-archive)**:
|
||||
```json
|
||||
{
|
||||
"archived": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (200): Updated repository object
|
||||
Reference in New Issue
Block a user