Skip to main content
Version: v1.0.0(int)

W005: Create API token (PAT)

Generates a Personal Access Token (PAT) for programmatic API access — CLIs, AI agents, third-party integrations. The plaintext token is returned once in the response; the backend stores only the SHA-256 hash. Lost plaintext → revoke (W007) and create a new one.

Token format: pharus_pat_<32 base62 chars> (43 chars). The branded prefix is meaningful to two consumers:

  • Auth middleware routes the bearer between PAT verification (this token) and Firebase ID-token verification.
  • Secret scanners (e.g. GitHub on push) flag accidentally committed tokens by prefix.

Steps

  1. Validate input. Trim the name; reject empty.
  2. Validate org binding (if provided). When orgId is non-null, call Platform.getMembership to confirm the caller is an active member. Return 403 otherwise.
  3. Compute expiry. When expiresInDays > 0, set expiresAt = now + days. Otherwise null.
  4. Generate plaintext. crypto.randomBytes(32) → base62-encode → prepend pharus_pat_.
  5. Insert the row. Call Platform.createApiToken with { userId, orgId, name, tokenPrefix (first 12 chars), tokenHash (SHA-256), expiresAt }.
  6. Return the row + the plaintext. The plaintext is never persisted server-side.

Returns

{ token: ApiToken, plaintext: string }.

Business rules

  • Plaintext shown once. The response is the only place the full token appears. Subsequent reads via W006 only return prefix + metadata.
  • Org binding overrides X-Org-Id. A PAT with orgId set ignores the request header on tenant routes; an unscoped PAT still requires the header.
  • No scopes. v1 PATs have a single permission: "full access as user". Fine-grained scopes are a future enhancement.

Errors

  • ForbiddenError — no userId on ctx (shouldn't happen — middleware enforces).
  • ForbiddenErrororgId provided and caller is not a member.
  • ValidationError — empty name.

Maps to

E005 POST /api/user/api-tokens.