9.9 KiB
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. |
|
Producer |
Core 涓珛鐗堬紙Increment 6a 鏀瑰啓锛夈€傛湰鏂囦欢鍚屾椂浣滀负 gitea adapter 鐨?reference implementation 绀轰緥锛坒rontmatter 鍘熸牱锛夛紱瀹炰緥鏈瀵圭収
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-Countresponse header = total results.Link: <url?page=2>; rel="next"鈥?walknextlinks 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 (50鈥?00 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 pushthe 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 withGET .../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 files 鈥?GET .../pulls?state=open, GET .../pulls/7/files
Search issues across repos 鈥?GET $BASE/api/v1/repos/issues/search?q=&type=pulls
List labels 鈥?GET .../labels?limit=50 | jq '.[] | {name,color}' (label create/update via POST/PATCH on the same path)
Raw file contents 鈥?GET .../raw/README.md (plain text; the contents/{path} variant returns base64 in .content)
Create a release 鈥?POST .../releases {"tag_name":"v1.2.0","target":"main",...}
Create a wiki page 鈥?POST .../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 page 鈥?GET|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/.../projectsor/users/.../projectsREST 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 upstreamraw/{pageName}orpages/{pageName}single-page endpoints, andpage/{pageName}only matches the mangled filename (see the wiki recipe above and [org-internal #3218]) 鈥?clone the wiki git repo (default branchmain) only if you need history beyond/wiki/revisionsor bulk filename surgery. - The authoritative route table is
routers/api/v1/api.goin 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.