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>
8.3 KiB
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:
{
"name": "string",
"scopes": ["all"]
}
Response (201):
{
"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):
{
"id": 1,
"login": "string",
"email": "string",
"is_admin": true
}
POST /orgs
Used in: Phase 1 Purpose: Create organization
Request:
{
"username": "string",
"full_name": "string",
"description": "string",
"visibility": "public"
}
Response (201):
{
"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):
{
"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):
{
"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):
{
"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):
{
"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):
{
"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):
{
"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):
[
{
"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:
{
"remote_address": "https://github.com/{owner}/{repo}.git",
"remote_username": "string",
"remote_password": "string (GitHub PAT)",
"interval": "8h",
"sync_on_commit": true
}
Response (201):
{
"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:
{
"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):
{
"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):
{
"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):
{
"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):
{
"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):
{
"archived": true,
"description": "[MOVED] Now at https://git.domain.com/org/repo — was: original description"
}
Request (un-archive):
{
"archived": false
}
Response (200): Updated repository object