Skip to Content
Getting StartedAuthentication

Authentication

platform-api routes are protected by ClerkAuthGuard, applied per-controller. The guard accepts two kinds of credential in the Authorization header:

CredentialHeaderBest for
Personal Access TokenAuthorization: Bearer mr_pat_…Scripts, CI, server-to-server, MCP, any non-browser integration
Clerk session JWTAuthorization: Bearer <clerk-jwt>Browser sessions where the user is already signed in via Clerk

The guard tries the PAT path first (any Bearer mr_pat_… token), and falls back to Clerk session verification otherwise.

agent-service endpoints (/api/chat, /api/agent, /api/graph, /api/pdf, /api/translate, /api/interview) are not Clerk-guarded at the controller level — they are designed to sit behind the gateway and be called by trusted callers. Do not expose them directly to untrusted clients without a proxy that enforces auth.

Personal Access Tokens (PAT)

A PAT is a long-lived bearer credential bound to your user. The plaintext is mr_pat_ + a base64url random string; the server stores only its SHA-256 hash, so the token is shown exactly once at creation.

Mint a token

You must already be authenticated (via a Clerk session) to create the first PAT. Tokens are created through the platform-api:

Create

curl -X POST http://localhost:3111/api/users/me/personal-access-tokens \ -H "Authorization: Bearer <clerk-jwt>" \ -H "Content-Type: application/json" \ -d '{ "name": "Codex on MacBook", "scopes": ["resume:read", "resume:write"] }'

Copy the token from the response

{ "code": 200, "data": { "id": "clx...", "name": "Codex on MacBook", "tokenPrefix": "mr_pat_AbCd1234", "tokenPreview": "mr_pat_AbCd1234...", "scopes": ["resume:read", "resume:write"], "token": "mr_pat_AbCd1234xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "createdAt": "2026-06-19T08:00:00.000Z" }, "message": "success", "timestamp": "2026-06-19T08:00:00.000Z" }

data.token is returned only on creation. Store it in a secret manager immediately. On every later listing you get tokenPreview (mr_pat_AbCd1234...) but never the full value again.

Use it

curl http://localhost:3111/api/resumes/mine \ -H "Authorization: Bearer mr_pat_AbCd1234xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Request body

FieldTypeRequiredNotes
namestring1–80 chars, human-readable label
expiresAtISO 8601 stringOptional expiry; omit for a non-expiring token
scopesstring[]Defaults to ["resume:read", "resume:write"]

Manage tokens

ActionMethod & path
List your tokensGET /api/users/me/personal-access-tokens
Create a tokenPOST /api/users/me/personal-access-tokens
Revoke a tokenPATCH /api/users/me/personal-access-tokens/:id/revoke

A revoked or expired token stops authenticating immediately. The server stamps lastUsedAt on each successful use, so you can audit activity in the listing.

Roles & RBAC

Routes can require roles via @Roles('user', 'admin') (the default, when unspecified, is admin). Your PAT inherits your user’s role — so a PAT for a normal user can hit user-level routes but not admin-only ones (for example, listing all feedback). If your role is insufficient, the API responds 401 with Insufficient permissions. Required: ….

Clerk session JWT

In a browser where the user is signed in through Clerk, send the session token Clerk issues:

const token = await window.Clerk.session.getToken() const res = await fetch('https://api.magic-resume.cn/api/resumes/mine', { headers: { Authorization: `Bearer ${token}` }, })

For non-browser integrations, prefer a PAT — it doesn’t expire with a session and is trivial to rotate and revoke.

Last updated on