PapermarkDocs

Authentication

Get a token and use it from the CLI, the API, and CI.

Every API call carries a bearer token. There are two ways to get one, and three places the CLI looks for one at runtime. This page covers both — pick the path that fits.

Token types

Where it comes fromFormatLifetime
Dashboard → Settings → API Tokenspm_live_…Long-lived; revoke from the dashboard
OAuth 2.1 device flowOpaque bearer string90 days; auto-refreshed by the CLI when offline_access is granted

Both forms are accepted in exactly the same Authorization: Bearer … header — the API doesn't care which kind it is. The split exists because dashboard tokens are mintable by humans (one click) while OAuth tokens are how an agent or third-party tool gets delegated access without seeing the user's password.

Scopes work the same for both: each token is restricted to the scopes you granted at creation time, and the API returns 403 forbidden if a request needs a scope the token lacks.

Getting a dashboard token

This is the fastest path. Use it for your own scripts, CI pipelines you control, and integrations where a human (you) will paste the token in.

  1. Open app.papermark.com, then Settings → API Tokens.
  2. Click Create token, name it, pick scopes (start with the smallest set that works — you can mint another with more scopes later), and choose Live or Test mode.
  3. Copy the token. It's shown exactly once — if you lose it, revoke it and mint a new one.

Getting an OAuth token (device flow)

Use this when you're building a CLI, a desktop integration, or any tool that ships to other people. The user authorizes once, your tool holds the token, and you never touch their password.

The CLI ships with the device-flow client baked in — running papermark login triggers it:

$ papermark login
Open https://app.papermark.com/oauth/device in your browser.
Enter code: WDJB-MJHT

You can pass --scopes documents.read,links.write to narrow the request (default is the union of all read scopes plus offline_access). The CLI polls every few seconds until you approve in the browser, then stores the access token + refresh token. Tokens expire after 90 days; with offline_access the CLI refreshes silently in the background.

Building your own client? Use OIDC discovery to find the device, token, and registration endpoints — they live on the canonical app host, not on api.papermark.com:

GET https://papermark.com/.well-known/openid-configuration

The response advertises device_authorization_endpoint, token_endpoint, and registration_endpoint. Register a unique public client_id of your own at the registration endpoint (open DCR, RFC 7591) — don't reuse papermark-cli, that's reserved for the official CLI. Then run the device flow per RFC 8628.

api.papermark.com is restricted to the /v1 REST surface — the OAuth and discovery endpoints are not reachable on that host. The CLI handles this for you (it strips the api. prefix to find the issuer); third-party clients should do the same.

Using the token

The CLI looks in three places, in order. The first one found wins:

  1. PAPERMARK_TOKEN environment variable
  2. PAPERMARK_CREDENTIALS_FILE — path to a JSON file like {"token": "pm_live_…", "apiUrl": "https://api.papermark.com"}
  3. The config file (~/.config/papermark/config.json on macOS/Linux, roaming AppData on Windows; XDG-aware)

This order is deliberate so a CI env var beats a developer's local config without anyone editing files. Run papermark whoami to see which source the active token is coming from.

Per-machine: the device flow

Run papermark login interactively. The token lands in the config file at 0600 permissions.

Per-script: paste a token

papermark login --token pm_live_…

Validates the token with one API call, then stores it. Same end state as the device flow, no browser.

CI: environment variable

export PAPERMARK_TOKEN=pm_live_…
papermark documents list --json

GitHub Actions:

- run: papermark documents list --json
  env:
    PAPERMARK_TOKEN: ${{ secrets.PAPERMARK_TOKEN }}

CI: credentials file

If your CI provider mounts secrets as files instead of env vars (Vault, some Kubernetes setups), point at the file:

export PAPERMARK_CREDENTIALS_FILE=/run/secrets/papermark.json
papermark documents list

The JSON shape is {"token": "...", "apiUrl": "..."} (apiUrl optional).

CI: pipe a token via stdin

For setups where you'd rather not have the token sit in an env var or file at all (short-lived workers, secret-rotation scripts), pipe it in:

echo "pm_live_…" | papermark auth set --stdin

The token is validated and persisted to the config file in one step, without a tempfile. The same command accepts a JSON object on stdin if you also want to set apiUrl.

Rotating and revoking

  • Dashboard tokens: revoke from Settings → API Tokens in the dashboard. The next request returns 401 unauthorized.
  • OAuth tokens: papermark logout clears the local token and refresh token. To revoke server-side too, the user can disconnect the integration from the dashboard.

Troubleshooting

papermark doctor runs a sequence of checks: config file readable, token present, API reachable, token still valid. Use it as the first step when something stops working.

On this page