Files

9.9 KiB
Raw Permalink Blame History

name, description, triggers, role
name description triggers role
gitea-rest Use ONLY when an agent must call the Gitea REST API over HTTP (curl / fetch / HttpClient) — not for other gitea tooling (the retired gitea-mcp wrapper / gitea_* MCP tools). Covers auth schemes + token scopes, pagination, error format, this fork's specifics, copy-paste recipes for the daily loop (issues, PRs, comments, labels, releases, raw files), and the on-demand recipe for extracting any endpoint's definition from the auto-generated swagger spec (`/swagger.v1.json`) instead of keeping an endpoint catalog in context. This is the only supported Gitea API path — the gitea-mcp wrapper is retired.
gitea rest
gitea api
gitea http
call gitea api
/api/v1
gitea swagger
swagger.v1.json
调gitea api
gitea 接口
gitea rest api
Producer

Core 中立版(Increment 6a 改写)。本文件同时作为 gitea adapter 的 reference implementation 示例(frontmatter 原样);实例术语对照 core/adapters/TERMINOLOGY.md

Gitea REST — conventions + on-demand endpoint lookup

Gitea ships a JSON REST API under /api/v1. The instance also publishes an auto-generated OpenAPI spec at /swagger.v1.json (~889KB, 316 endpoints) — never load it whole into context; extract single endpoint definitions with jq (see "Endpoint lookup"). The spec is generated from this fork's code, so it is always current — no manually maintained catalog can drift.

Quick start

BASE=<instance-base-url>
TOKEN=$(cat <config-home>/octopus/gitea-token)   # or $WORKSPACE_GIT_TOKEN inside a workspace container
AUTH="Authorization: token $TOKEN"
curl -fsS -H "$AUTH" $BASE/api/v1/version    # => {"version":"1.22.0"}

Inside a workspace container: WORKSPACE_GIT_TOKEN already carries the provisioned token, and the instance host resolves to the VPC-internal address (no EIP bandwidth cap) — use it as TOKEN directly.

Authentication

Scheme Header Notes
PAT (recommended) Authorization: token <PAT> Also accepts bearer. 40-char hex.
OAuth2 JWT Authorization: bearer <jwt> OAuth2-app token.
Basic Authorization: Basic <base64(user:pass)> Server must enable it; 2FA needs X-Gitea-OTP.
  • Query-param token (?token=) is deprecated — avoid.
  • Sudo: <username> header acts as another user (admin only).
  • Keep tokens out of git and out of process argv where possible: read from a file/env into a shell var at call time, not in a committed script.

Token scopes (write implies read)

Categories: issue, repository, organization, user, notification, package, admin, misc, activitypub — each as read:X / write:X. HTTP method sets the level: GET → read, POST/PUT/PATCH/DELETE → write. A scope miss returns 403 with token does not have at least one of required scope(s)... — re-issue the token rather than widening other permissions.

The full agent loop (push commits, create/merge PRs, comment) needs at minimum write:repository + write:issue. Workspace container tokens are provisioned read:repository by default — write operations fail with 403 until the operator widens the scope.

Pagination

  • page (1-based, default 1), limit (default 30, hard cap 50).
  • X-Total-Count response header = total results.
  • Link: <url?page=2>; rel="next" — walk next links until absent.
  • Some endpoints (commits, pulls) also set X-Page / X-PerPage / X-PageCount / X-HasMore.

Errors

Every error is {"message":"...","url":".../api/swagger"} with the matching status: 400 bad arg, 403 permission/scope, 404 not found or wrong BASE, 409 already exists, 413 too large, 422 malformed JSON / missing field. There is no rate limiting on /api/v1 — don't expect X-RateLimit-*.

Endpoint lookup (swagger, on demand)

For any endpoint not covered by the recipes below, pull the spec once per session and jq out just the definition you need (50200 tokens each):

curl -s $BASE/swagger.v1.json -o /tmp/gitea-sw.json
jq '.paths["/repos/{owner}/{repo}/pulls"].post' /tmp/gitea-sw.json          # create PR
jq '.paths["/repos/{owner}/{repo}/issues"].get.parameters' /tmp/gitea-sw.json
jq '.definitions.CreateIssueOption' /tmp/gitea-sw.json                       # a body model
jq -r '.paths | keys[]' /tmp/gitea-sw.json | grep actions                   # discover endpoints

Definitions include parameter names/types/required flags and $ref response models (under .definitions on this Gitea version). Prefer discovering via the spec over guessing paths.

Daily-loop recipes

BASE/TOKEN/AUTH as in Quick start; OWNER=Octopus REPO=octopus as the example. All bodies are JSON.

Create an issue

curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"title":"Bug: X fails","body":"steps...","labels":[12]}'

Comment on an issue / PR (same path for both — PRs are issues by index)

curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/issues/42/comments" -H "$AUTH" \
  -H 'Content-Type: application/json' -d '{"body":"LGTM"}'

Create a PR

curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"head":"workflow/fix/x","base":"main","title":"fix: X"}'

Merge a PR

curl -fsS -X POST "$BASE/api/v1/repos/$OWNER/$REPO/pulls/7/merge" -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"Do":"merge","MergeTitleField":"feat: add X","MergeMessageField":"closes #7"}'

Merge returned 405? Read the response body — two distinct causes:

  • {"message":"head branch is behind the base branch"} — the keep-mergeable bot advanced main after your push, or Gitea's mergeable cache is stale. Fix: git fetch origin main && git merge origin/main && git push the head branch, wait ~2s, retry the merge. (Recurring on this server — seen 3 PRs in a row on 2026-08-23.)
  • {"message":"The PR is already merged"}treat as success. The merge landed during a previous "failed" attempt (racy async recompute). Confirm with GET .../pulls/7 | jq '.merged, .merge_commit_sha' before assuming failure. Never re-push/re-create the PR on this signal.

Auth hygiene — always pass the token via the Authorization header (-H "Authorization: token $TOKEN"), never embedded in a remote URL (https://user:token@host/...): it persists into .git/config and shell history. If a token leaks into a remote URL, rewrite the remote (git remote set-url) and rotate.

List PRs / changed filesGET .../pulls?state=open, GET .../pulls/7/files

Search issues across reposGET $BASE/api/v1/repos/issues/search?q=&type=pulls

List labelsGET .../labels?limit=50 | jq '.[] | {name,color}' (label create/update via POST/PATCH on the same path)

Raw file contentsGET .../raw/README.md (plain text; the contents/{path} variant returns base64 in .content)

Create a releasePOST .../releases {"tag_name":"v1.2.0","target":"main",...}

Create a wiki pagePOST .../wiki/new {"title":"My Page","content_base64":"<b64>","message":"add page"}. content_base64 is the ONLY field that writes body text — this fork silently ignores the upstream-style content field on wiki create/update (no error, 201 returned, page saved 0 bytes; evidence [org-internal #3944]: commits f5eaf18/4f0217d/bad5880). content is valid only on the file API, never on wiki.

Read / edit / delete a wiki pageGET|PATCH|DELETE .../wiki/page/{pageName}. Read by the mangled name, not the logical title: this fork rewrites stored filenames for titles containing /, spaces, or : (slash percent-encoded into the filename + a .- suffix — see [org-internal #3218]), so GET /wiki/page/bugfix-3204/bugfix-report 404s. Always GET .../wiki/pages first and use the returned sub_url verbatim (it already carries the %2F encoding and .- suffix). PATCH/DELETE take the same mangled pageName. For PATCH, omit title to keep the page name and send only content_base64+message (fixed in gitea dev-421-g7ff56aec08, [org-internal #3510] — before that a title-less PATCH silently renamed the page to unnamed.md and later edits deleted target pages). Same trap as create: a PATCH carrying content instead of content_base64 returns 200 with a 0-byte page ([org-internal #3944]) — always base64-encode the body and verify non-empty via a follow-up GET.

This fork's specifics

  • Projects are repo-level only — no /orgs/.../projects or /users/.../projects REST endpoints.
  • Aggregated inline review comments: no single list-all endpoint — list reviews, then fetch each review's /comments.
  • Wiki REST = 6 method×path combos on 4 paths (new, page/{pageName} GET/PATCH/DELETE, pages, revisions/{pageName}). This fork has no upstream raw/{pageName} or pages/{pageName} single-page endpoints, and page/{pageName} only matches the mangled filename (see the wiki recipe above and [org-internal #3218]) — clone the wiki git repo (default branch main) only if you need history beyond /wiki/revisions or bulk filename surgery.
  • The authoritative route table is routers/api/v1/api.go in the Gitea source; the human-readable docs UI is <BASE>/api/swagger.

In Effect code

Prefer HttpClient from @effect/platform with the same headers (see rules/effect-rules on the wiki). Outside Effect, curl / Bun.fetch are fine — the wire format is identical.