GitHub
Reference

Authentication

Bearer-token auth — Personal Access Tokens for humans and workflow tokens for automation, both verified via the same JWT middleware.

Every API request needs a Authorization: Bearer <token> header (or x-api-key: <token> on the LLM gateway, Anthropic-compatibility). The middleware in internal/auth/auth.go parses and validates the token, then injects userId, email, and orgId into the request context.

Token types

TypeFormatIssued byUse for
Personal Access Token (PAT)pfai_ + 40 hex charsUser from Settings → Personal Access TokensScripts, CI, CLI in non-interactive mode
Workflow execution tokenStandard JWT (HS256)Workflow runtime, automaticAgent containers, pipelines, anywhere PFAI_TOKEN is auto-set
OIDC session tokenStandard JWTAfter OIDC login at /api/v1/oidc/authorizeBrowser; cookie-set, you don’t see it directly

All three flow through the same JWT validator. PAT scopes restrict what calls succeed; OIDC sessions inherit the user’s full RBAC; workflow tokens carry the executing workflow’s runtime_config.permissions (defaulting to the dispatcher’s role if unset).

curl http://localhost:3000/api/v1/issues \
  -H "Authorization: Bearer pfai_a1b2c3..."

Creating a PAT

Settings → Personal Access Tokens → New token

Name + scopes + expiration. The token value is shown once at creation — it’s stored as sha256(token) thereafter, so you can’t recover it. Treat it as a credential.

pfai token create --name "ci" --scopes write,pipeline --expires 90d
# Returns: pfai_a1b2c3d4...

Scopes

11 scopes cover most use cases (models.go — search for TokenScope):

ScopeImplies
readAll *:read scopes
writeread + all *:write scopes + pipeline
repo:read · repo:writeForge browse + push, releases, deploy keys
issues:read · issues:writePlan API
pr:read · pr:writePull-request review and merge
admin:read · admin:writeSuper-admin endpoints under /api/v1/admin/*
pipelineDispatch and manage CI/CD runs

Scope hierarchy: write ⊇ read, *:write ⊇ *:read. A read-scoped token gets 403 on POST /api/v1/issues; repo:read gets 403 on POST /api/v1/forge/{owner}/{repo}/pulls.

PAT scopes layer on top of RBAC permissions. The effective check is scope ALLOWS the operation AND user's role has the required permission. A write-scoped PAT for a Member-roled user still can’t delete projects (lacks projects.delete).

Verifying a token

curl http://localhost:3000/api/v1/me/permissions \
  -H "Authorization: Bearer $PFAI_TOKEN"

Returns the effective permission set the request would carry. Useful for “does this token actually let me do X?” checks before running a sensitive operation. Returns 401 if the token is invalid/expired.

{
  "userId": "user_abc",
  "email": "[email protected]",
  "orgId": "org_default",
  "permissions": ["issues.read", "issues.create", "issues.edit", "code.read", ...]
}

Workflow tokens

When a workflow execution starts, the runtime mints a standard JWT (HS256) via GenerateExecutionToken and injects it into the agent container as PFAI_TOKEN along with PFAI_SERVER and PFAI_EXECUTION_ID.

The JWT carries these claims:

ClaimPurpose
subThe execution ID
execution_idSame — duplicated in the namespaced claim
workflow_idWorkflow this execution belongs to
org_idTenant scope
permissionsArray of dot-notation perms (e.g. ["issues.read", "code.write"]); from runtime_config.permissions, else empty (deny-all default)
token_typeConstant "execution" to distinguish from PATs
issConstant "proxifai"
expNow + 24 hours

The signing secret is PFAI_JWT_SECRET (with JWT_SECRET as fallback). The same JWT validator that handles PATs also accepts these — same Authorization: Bearer … header.

Default is deny-all. If runtime_config.permissions isn’t set on the workflow, the execution token gets an empty permission array and every API call returns 403. Grant explicit permissions like ["issues.read", "code.read"] on the workflow to give containers the access they need.

There is a separate HMAC-based token format (pfai_<execID>_<sig16>) used only by the LLM gateway middleware (llmgateway/middleware/auth.go) to authenticate gateway requests cheaply without a JWT round-trip — this is invisible to API consumers; you don’t construct or pass it manually.

Workflow tokens aren’t creatable outside an execution. For long-lived service access, mint a PAT.

Browser sessions (OIDC)

When the platform is configured with OIDC (OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET), users land on /api/v1/oidc/authorize to start the flow. After consent, the cookie set is a standard signed JWT — it works against the same API surface as a PAT. Programmatic clients should use PATs instead; cookies are for the browser SPA.

Errors

StatusMeaning
401 UnauthorizedMissing/invalid Authorization header, expired token, or HMAC mismatch on workflow tokens
402 Payment RequiredCredit check failed (Enterprise/SaaS only, requires account-api)
403 ForbiddenToken is valid but lacks the required scope or RBAC permission

The body shape is {"error":"<message>"} — see Errors.

See also