<!-- SKILL-VERSION: pdf-auth 3ed568e@2026-06-05T18:19:05+10:00 -->
> **Skill version:** `3ed568e@2026-06-05T18:19:05+10:00` (`pdf-auth`).
> If you have a cached copy of this skill with a different version token,
> discard it and use this copy. Re-fetch this URL at the start of each
> session to detect updates.

# makesPDF — authentication

Companion to [`pdf-template-author.md`](./pdf-template-author.md). Read
this once, when you don't have a Bearer token; once you do, you don't
need to come back. Every `/api/v1/*` endpoint requires a Bearer token —
anonymous access does not exist, even in dev, even for validation.

## **Don't ask the user for an email and password.**

Run the OAuth device flow instead. It exists so AI agents and CLIs can
authenticate without ever handling the user's credentials.

## Device authorization flow (RFC 8628)

### 1. Request a code

No auth required:

```
POST /api/v1/device/code
Content-Type: application/json

{ "client_name": "Claude" }
```

Response:

```json
{
  "device_code": "<long opaque string, keep secret>",
  "user_code": "ABCD-1234",
  "verification_uri": "https://makespdf.com/device",
  "verification_uri_complete": "https://makespdf.com/device?code=ABCD-1234",
  "expires_in": 600,
  "interval": 5
}
```

### 2. Show the user the verification URL and code

A good prompt:

> "To let me render PDFs on your account, open
> **https://makespdf.com/device?code=ABCD-1234** in your browser and
> click Approve. The code is **ABCD-1234** — make sure it matches what
> the page shows. **Tell me when you've approved and I'll finish up.**"

**If you have browser tools, stop browsing now.** Do not open the
verification URL, do not navigate, do not click — your part of the flow
is done until the human signs in and approves. Polling (step 3) is a
server-to-server API call; no browser needed. Opening `/device` yourself
will hit a login screen, and the account that gets charged is whoever
signs in — so unless your user is already signed in on that profile,
you are about to register or log in on their behalf. Don't.

### 3. Wait for the user to approve, then fetch the token

Do not poll. Ask the user to tell you when they've clicked Approve;
when they do, make **one** request to exchange the device code for an
access token:

```
POST /api/v1/device/token
Content-Type: application/json

{ "device_code": "<from step 1>" }
```

- HTTP 200 `{ "access_token": "mpdf_...", "token_type": "Bearer" }` —
  you're done.
- HTTP 400 `{ "error": "authorization_pending" }` — the approval hasn't
  landed yet. Ask the user to double-check the browser page and tell
  you when the "Approved" confirmation is visible, then try once more.
- HTTP 403 `{ "error": "access_denied" }` — user declined. Stop.
- HTTP 410 `{ "error": "expired_token" }` — the 10-minute window
  elapsed. Start over from step 1.

Why not poll: you have a human in the loop. The user is the trigger,
not a clock. Polling while waiting scrolls the approval prompt out of
the user's view and adds no information. Make a single call when the
user says they're ready.

_Automated harnesses_ with no interactive user (CI smoke tests,
scripted demos) may fall back to polling `/device/token` on the
`interval` from step 1. This is allowed but noisy — prefer user-driven
confirmation when a user is present.

### 4. Use the token

Set `Authorization: Bearer <access_token>` on every subsequent
`/api/v1/*` request. Persist it locally so you don't have to re-run the
flow next time.

## Don't do this

- ❌ **Asking the user for an email and password directly.** There is no
  password endpoint to send them to.
- ❌ **Asking the user to paste an API key when no token is available.**
  The device flow is faster, safer, and doesn't require them to leave
  their current task to go generate one.
- ❌ **Registering an account on the user's behalf.** If the device approval
  page (`/device?code=…`) shows a login screen when you open it in a
  browser MCP, stop. Tell the user: "I've requested a device code — please
  sign in at \<verification_uri_complete\> and click Approve." Wait for
  them. Never click through a registration form or submit credentials
  yourself. The account owner is the person whose credits get charged; it
  must be a human decision.
- ❌ **Signing in with credentials the user has not just typed into your
  chat.** Even if you have them from a previous turn, re-prompt or hand
  control back — do not auto-fill the login form.
- ❌ **Calling `/api/v1/preview` without a Bearer token and hoping it
  works.** `/preview` requires auth; anonymous access does not exist
  on that endpoint.
- ⚠️ **`/api/v1/md` accepts anonymous calls when the operator has
  enabled the promo.** Per-IP rate limits (60/hour, 200/day, 20 pages
  per render) and no artifact capture apply. Useful for quick demos
  or low-volume scripting from environments without an API key.
  Authenticate anyway if your workload would benefit from higher
  limits, `X-Artifact-Id`-based re-downloads, or template-based
  rendering — anonymous mode is a wedge, not a long-term home for
  serious integrations.

If a request returns 401, parse the response body — it contains a
`device_authorization` block with the exact URLs to use. The 401
payload also includes pointers to this skill file and the docs index
so an agent that landed here without context can self-orient.
