Public REST API
Every fremforge admin-UI action has a public REST API equivalent. The admin UI is itself a client of this surface, and AI agents acting on behalf of users consume the same endpoints. There are no admin-only endpoints and no surprise gaps.
Get the spec
The canonical machine-readable specification is OpenAPI 3.1.
- YAML: /api/openapi.yaml, canonical, single-file, downloadable for tooling.
- JSON mirror: published at
/api/openapi.jsonat launch from the same source, for clients that prefer JSON.
Both files are versioned with the API and update on every fremforge release. The OpenAPI version field tracks MAJOR.MINOR.PATCH; the API itself is versioned in the path (/api/v1/...) for breaking changes.
Authentication
Three authentication paths, declared in the spec as separate securitySchemes so consumers can pick the right one.
Personal access tokens (PAT), for human callers and CI integrations
Personal access tokens are user-scoped, not org-scoped, so they live under your user settings on Forgejo: navigate to /<your-user>/settings/applications on the Forgejo UI (e.g. frem.sh/<your-user>/settings/applications). Tokens carry a name, a scope set, and a TTL up to 365 days. The raw token is shown once; only its hash is stored server-side.
Distinct from org API tokens (covered below under Token management) which live in fremforge admin at /<org>/_admin/api-tokens and survive individual member departures. Use a PAT for personal scripts and local development; use an org API token for CI and production automation.
curl -H "Authorization: Bearer ${FREMFORGE_PAT}" \
https://frem.sh/_app/api/v1/users/meOAuth 2.0 client credentials, for machine-to-machine
Standard RFC 6749 client-credentials grant. Register a client at the org level, exchange client_id + client_secret at the token endpoint:
curl -X POST https://frem.sh/_app/api/v1/auth/oauth/token \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
-d "scope=org:read seats:write"OIDC token exchange, for IdPs and workflow runners
When your IdP can mint OIDC JWTs (Authentik, Entra, Okta, Keycloak, …) or when a fremforge Forgejo Actions workflow needs to call back into the API, prefer POST /api/v1/auth/token-exchange over long-lived PATs. No shared secret leaves your boundary, the IdP rotates the underlying key on its own schedule.
Accepted issuers:
- Customer IdP, registered as an
auth_sourcesentry for the target tenant (the same JWKS already used for member sign-in). Wire your IdP from the tenant SSO settings page first. - fremforge runner-OIDC, Forgejo Actions runner steps can self-mint a fremforge token without any extra IdP wiring. The issuer URL is published in the runner-job environment as
FREMFORGE_OIDC_ISSUER.
Submitted JWTs MUST carry aud=fremforge-api and a recent exp. The returned access token is an ordinary ffp_ PAT, pass it on subsequent calls as Authorization: Bearer <token>. TTL is hard-capped at 1 hour. Scopes are the intersection of the request and the exchange allow-list; * and tokens:manage are never grantable here, for those, use POST /api/v1/auth/tokens or the admin UI.
# 1. Mint a JWT from your IdP (aud=fremforge-api).
ASSERTION="$(your-idp-cli get-token --audience fremforge-api)"
# 2. Exchange it for a short-lived fremforge access token.
ACCESS_TOKEN="$(curl -s -X POST \
-H 'Content-Type: application/json' \
-d "{\"subject_token\":\"$ASSERTION\",\"tenant_slug\":\"acme\",\"scopes\":[\"exports:write\",\"exports:read\"]}" \
https://frem.sh/_app/api/v1/auth/token-exchange | jq -r .access_token)"
# 3. Drive the API with the resulting token (TTL = 1 hour).
curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
https://frem.sh/_app/api/v1/users/meFrom a Forgejo Actions workflow step the same flow looks like:
- name: Exchange runner OIDC for fremforge token
run: |
ASSERTION="$FREMFORGE_OIDC_TOKEN" # injected by the runner controller
echo "FREMFORGE_TOKEN=$(curl -sX POST \
-H 'Content-Type: application/json' \
-d "{\"subject_token\":\"$ASSERTION\",\"tenant_slug\":\"${{ github.repository_owner }}\",\"scopes\":[\"sboms:write\"]}" \
https://frem.sh/_app/api/v1/auth/token-exchange | jq -r .access_token)" >> $GITHUB_ENVThe endpoint is unauthenticated by design (the JWT is the auth) but rate-limited at 30 attempts/minute per source IP. Bursts beyond that return 429 Retry-After. Each rejected exchange still costs an RSA verify, so a leaking IdP credential or a misbehaving CI loop is contained rather than amplifying.
Agent-identity OAuth (Phase 2+), for AI agents acting on behalf of users
The OpenAPI spec uses the x-fremforge-phase extension to mark endpoint availability:
x-fremforge-phase: 1, live and available now.x-fremforge-phase: 2, specified but not yet implemented; returns501 Not Implemented.
Endpoints without the extension are Phase 1. Attempting a Phase 2 endpoint returns {"error":"not_implemented","phase":2}.
The agent authenticates as the agent, on behalf of a named user, under a scoped delegated mandate the user has issued. RFC 8693 token-exchange shape with an on-behalf-of assertion. Audit-log records actor=agent:<agent_id>, on_behalf_of=<user_id> so agent actions are visibly distinct from human actions.
This flow is not yet live at launch. The endpoints are documented in the spec ahead of implementation so the admin UI and the monolith can be built against the final shape. See the AI posture page for the full roadmap.
Agent usage patterns
Three reference scenarios the spec was designed around. If your agent needs something the spec does not support cleanly, that is a reportable gap, tell us at support@frem.sh.
Scenario 1, Provisioning a new org from a delegated mandate
An agent acting on behalf of a reseller or admin mandate can provision a complete org in four API calls:
POST /orgs, create the org with slug, display name, and billing contactPUT /orgs/{org}/sso, configure OIDC or SAML SSO for the new orgPOST /orgs/{org}/seats, provision the initial seat countGET /orgs/{org}, verify the org is active and SSO is configured
Scenario 2, Reading findings and creating remediation PRs
A read-scoped mandate. The agent polls the findings endpoints, identifies high-severity items, and opens remediation PRs through the standard Forgejo Git interface (out of scope for this REST API; the agent uses Git over HTTPS or SSH for that part). Audit-log entries appear with the distinctive agent-actor marker.
Scenario 3, Adjusting runtime policy during an incident
Tightly-scoped, time-boxed mandate (policy:write:scope=findings-only, valid 1h). The agent applies stricter policy (e.g., merge-block on CRITICAL CVEs across all repos), the change is audit-logged with the agent’s identity and the on-behalf-of user, and org owners see the change in the audit-stream filter.
Versioning policy
- Major versions in the path (
/v1/...,/v2/...). Breaking changes ship in a new major version with a minimum 12-month overlap during which both versions are supported. - Minor and patch versions in
info.version. Additive endpoints, new optional fields, and documentation improvements land within the current major version without breaking compatibility. - The OpenAPI spec is the source of truth; if a behaviour differs from the spec, the spec is correct and the implementation will be patched. Report deviations to support@frem.sh.
Rate limits
API requests are rate-limited per token:
| Request type | Limit |
|---|---|
Read (GET) | 600 requests/minute |
Write (POST, PUT, PATCH, DELETE) | 60 requests/minute |
Exceeding a limit returns HTTP 429 with a Retry-After header indicating when the window resets. Limits apply per token, not per IP. Org API tokens and PATs share the same per-token limits.
Every response also carries IETF RateLimit and RateLimit-Policy headers per draft-ietf-httpapi-ratelimit-headers. Per-token rate limits and per-org rate limits both apply; the more restrictive of the two is reported in the response headers.
Validating the spec locally
Download the spec, then run a linter against the local file:
curl -fsSL https://docs.frem.sh/api/openapi.yaml -o openapi.yaml
# Redocly CLI
npx @redocly/cli@latest lint openapi.yaml
# Swagger CLI
npx @apidevtools/swagger-cli@latest validate openapi.yamlThe spec validates clean against both linters as a Phase 1 launch-blocker commitment.
What this spec does not cover
Two surfaces are intentionally out of scope:
- Forgejo-native API, endpoints under
/api/v1/repos,/api/v1/users,/api/v1/issues,/api/v1/pulls, etc. Those are the Forgejo/Gitea API surface that fremforge exposes unmodified athttps://frem.sh/_app/api/v1/.... Each running fremforge instance also serves the live Swagger UI athttps://frem.sh/api/swagger(rendered straight from the running Forgejo, so it’s always in sync with the deployed version). The migration guides reference these endpoints directly because they are the standard Forgejo surface. - Webhook delivery payloads, Forgejo’s webhook event shapes are upstream-defined. The fremforge admin UI lets you configure webhooks; payloads use the Forgejo event vocabulary (
X-Forgejo-Eventheader,X-Forgejo-SignatureHMAC-SHA256).
The OpenAPI spec covers the fremforge-specific control plane: organisations, seats, policy, findings, runners, exports, audit, mandates. Anything that exists because fremforge added it on top of Forgejo lives in this spec.
Token management
Two token types are available.
Token types
| Type | Tied to | When to use |
|---|---|---|
| Org API token | The org, not a user | CI systems, automation, and integrations that act on behalf of the org. Survives individual member departures. |
| Personal access token (PAT) | An individual user account | Local development, personal scripts, and cases where the caller identity should reflect a specific person in the audit log. |
Rule of thumb: use an org API token for anything that runs in production or CI. Use a PAT for personal tooling and development workflows.
Creating an org API token
- Go to Org admin → API tokens → Mint a new token (
frem.sh/<org>/_admin/api-tokens). - Enter a descriptive name (e.g.
ci-deploy,renovate-bot). - Select scopes. Available scopes:
| Scope | Access granted |
|---|---|
read:org | Read org metadata, membership list, settings |
write:org | Modify org settings and membership |
read:repo | Clone and read all org repositories |
write:repo | Push, create branches, manage repository settings |
read:audit-log | Read the org audit log |
write:secrets | Create and update org and repo secrets |
read:packages | Pull packages from the org package registry |
write:packages | Push packages to the org package registry |
- Set an expiry. Default TTL (when
ttl_daysis omitted) is 90 days. Maximum TTL is 365 days. Users can specify any value from 1 to 365 in thettl_daysfield at creation time. Tokens cannot be set to never expire. - Click Generate token.
The token value is shown exactly once at creation. Copy and store it immediately in your secret manager. fremforge does not store the raw value; only its hash is retained server-side. If you lose the value, revoke the token and create a new one.
Creating a PAT
- Go to User settings → Applications → Generate new token (
frem.sh/user/settings/applications). - Enter a name, select scopes, and set an expiry (default 90 days; maximum 365 days).
- Click Generate token.
PATs use the same scope model as org API tokens. A PAT inherits the access of the user who created it, further constrained by the selected scopes.
Token isolation
Org API tokens are strictly org-scoped. A token created under org acme cannot access any resource in org beta, even if the user who created the token is a member of both orgs. This is a platform-enforced boundary, not a permission check that can be bypassed.
This means you need one org API token per org for any multi-org automation. There is no cross-org token type.
Rotating a token
Rotate tokens when they are approaching expiry, when a team member who knew the value leaves, or when a token may have been exposed.
Rotation procedure:
- Create the new token at Org admin → Settings → Applications → New token.
- Update every consumer of the old token: CI secrets, deployment configs,
.env.deploy.localfiles, third-party integrations. - Verify the new token works end-to-end by triggering a test run or making a test API call.
- Revoke the old token at Org admin → Settings → Applications → find the token → Revoke.
Create first, update consumers, then revoke. Never revoke before the replacement is in place.
Revoking a token
- Go to Org admin → Settings → Applications (for org API tokens) or User settings → Applications (for PATs).
- Find the token by name.
- Click Revoke.
Revocation is immediate. Any request using the revoked token receives a 401 Unauthorized response. There is no grace period.
Listing active tokens
Org admin → Settings → Applications shows all org API tokens with:
- Token name
- Creator username
- Creation date
- Last-used date
- Expiry date
- Scopes
Token values are not visible after creation. Org owners can see all token metadata regardless of which owner created the token. Only the token creator saw the value at creation time.
For PATs, each user can list their own tokens at User settings → Applications. Org owners cannot enumerate another user’s PATs, but a removed member’s PATs automatically lose access to org resources the moment that member is removed from the org.
Token expiry
Tokens expire automatically at their configured TTL. An expired token returns 401 Unauthorized. Expired tokens cannot be renewed. Create a new token and update consumers; then optionally delete the expired token from the list.
To avoid outages from silent expiry, set an alert in your monitoring system based on the expiry date shown in the token list, or rely on the notification email fremforge sends 7 days before an org API token expires (sent to org owners).
Security practices
- Store tokens as encrypted CI secrets, not in repository files, workflow YAML, or commit history.
- Use the minimum scope set needed for the task. A token that only reads repos does not need
write:org. - Set the shortest TTL that is operationally practical. The default is 90 days; 365 days is the maximum.
- Rotate tokens that may have been exposed rather than assuming they were not used.
- Audit token last-used dates periodically. A token that has not been used in 30 days may be a stale credential from a decommissioned integration.
Get help
API behaviour questions, missing endpoints, or proposals for new operations: support@frem.sh with subject line starting API -.