Skip to content

thfg vs thfg api vs curl: when to use each

Use this guide to choose the right level of control for a task.

The default HTTP contract for this ecosystem is:

  • Forgejo Proxy is the canonical API surface
  • API families live under /api/v1/...
  • /health stays rooted
  • ActivityPub keeps rooted public federation URLs, while the runtime also accepts /api/v1/activitypub/... aliases for API clients

That means:

  • use thfg <command> first
  • use thfg api /... for anything under /api/v1, including canonical /activitypub/... aliases when you want raw payloads
  • use thfg activitypub ... for ergonomic ActivityPub access
  • use curl for /health, streaming/multipart work, or when you need the rooted public /activitypub/** URL itself

If you are migrating older scripts, read Migrate to the normalized /api/v1 API surface.

ApproachUse it whenExample
thfg <command>A dedicated CLI command already existsthfg repo view myorg/myrepo
thfg apiYou need an /api/v1 endpoint that has no dedicated command yetthfg api /repos/myorg/myrepo/topics
curlYou need streaming, multipart uploads, raw status handling, or a root-path exceptioncurl "$PROXY_URL/health"

Prefer a dedicated command whenever one exists. It handles host resolution, authentication, output formatting, and repo detection for you.

Terminal window
# Repositories
thfg repo list
thfg repo view myorg/myrepo
# Issues and pull requests
thfg issue list --repo myorg/myrepo
thfg pr merge 7 --repo myorg/myrepo
# Actions
thfg run list --repo myorg/myrepo
thfg run logs --repo myorg/myrepo --job failed
# Projects
thfg project list --org myorg
thfg project cards --id 2 --column 7

Use thfg --help or thfg <command> --help to discover the current command surface.

2. Use thfg api for normalized /api/v1 escape-hatch calls

Section titled “2. Use thfg api for normalized /api/v1 escape-hatch calls”

thfg api is the generic client for the proxy’s normalized API surface.

  • The positional path must start with /
  • The path is joined onto the active /api/v1 base URL
  • The output is raw JSON
  • Canonical ActivityPub aliases such as /activitypub/actor are available through thfg api
Terminal window
# GET (default)
thfg api /repos/myorg/myrepo/topics
# Paginate an array endpoint
thfg api /user/repos --paginate
# PATCH with JSON body fields
thfg api /repos/myorg/myrepo \
--method PATCH \
--field description="Updated description"
# POST from a file or stdin
thfg api /repos/myorg/myrepo/issues \
--method POST \
--input issue.json
# Add custom headers
thfg api /repos/myorg/myrepo \
--header "X-Forgejo-Host: https://codeberg.org"
# View the merged proxy spec
thfg api spec
  • the endpoint is part of /api/v1 but not wrapped by a CLI command yet
  • you want to reuse the active host, token, and proxy configuration
  • you want a simple JSON response that you can pipe to jq
  • you need log streaming or another long-lived response
  • you need multipart uploads
  • you need raw response codes or custom retry behaviour
  • you need /health
  • you specifically need the rooted public /activitypub/** URL instead of the canonical alias

For ActivityPub resources, use the dedicated commands instead:

Terminal window
thfg activitypub instance actor
thfg activitypub user outbox @me
thfg activitypub repo actor myorg/myrepo

Use curl directly when the CLI abstraction gets in the way.

Terminal window
TOKEN="$FORGEJO_TOKEN"
PROXY_URL="https://forgejo-proxy.hochguertel.work"
REPO="myorg/myrepo"
# Raw GET against the canonical proxy API surface
curl -s -H "Authorization: token $TOKEN" \
"$PROXY_URL/api/v1/repos/$REPO/topics" | jq .
# Streaming logs still belong in curl
curl -N -H "Authorization: token $TOKEN" \
"$PROXY_URL/api/v1/repos/$REPO/actions/runs/latest/logs?job=failed"
# Root exception: health
curl -s "$PROXY_URL/health"

Direct Forgejo REST is now mainly a debugging comparison tool. Reach for it when you need to answer questions like:

  • “Is this failure in the proxy layer or upstream Forgejo?”
  • “Does Forgejo return the same payload the proxy is forwarding?”
  • “Is this endpoint truly proxy-only, or is it native upstream?”
Terminal window
BASE_URL="https://pastoral-oyster.pikapod.net"
# Compare upstream Forgejo directly
curl -s -H "Authorization: token $TOKEN" \
"$BASE_URL/api/v1/repos/$REPO/topics" | jq .

Use the proxy contract under /api/v1/projects/..., not the old /api/v1/proxy/... paths.

Terminal window
curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "X-Webui-Username: $FORGEJO_WEBUI_USERNAME" \
-H "X-Webui-Password: $FORGEJO_WEBUI_PASSWORD" \
-H "Content-Type: application/json" \
-d '{"issue_id":7,"owner":"myorg","repo":"myrepo"}' \
"$PROXY_URL/api/v1/projects/2/columns/7/cards"

Use the dedicated CLI commands for normal work. Drop to the rooted path only when you need the published federation URL itself.

Terminal window
thfg activitypub instance actor
# raw canonical alias through thfg api
thfg api /activitypub/actor
# or
curl -s -H "Authorization: token $TOKEN" \
"$PROXY_URL/activitypub/actor"
NeedRecommended tool
Standard repo/issue/PR/project workflowthfg <command>
Missing /api/v1 endpointthfg api
/healthcurl
Canonical ActivityPub API aliasthfg activitypub or thfg api /activitypub/...
Rooted public /activitypub/** URLcurl (or thfg activitypub for equivalent data access)
Streaming or multipartcurl
Compare proxy vs upstream Forgejocurl against both hosts