Skip to main content
Developers

Authentication: API Keys and OAuth

API keys for service-to-service calls, OAuth 2.0 for user-context apps, and how to handle key rotation.

Authentication: API Keys and OAuth

Two authentication models supported:

Use case Use this
Service-to-service (your backend → Papyrus) API key
User-context app (a web app where users are humans) OAuth 2.0 + PKCE

API keys

Create under Admin → API Keys. Each key has:

  • A name (for your reference)
  • A set of scopes (documents:read, documents:write, workflows:write, analytics:read, etc.)
  • An optional IP allowlist
  • A rotation timestamp

Use:

curl https://yourtenant.papyrus.io/api/v1/documents \
  -H "Authorization: Bearer pk_live_xxxxxxxxxxxx" \
  -H "X-Tenant-Id: yourtenant"

Keys are revocable from the same admin page. Audit log captures every key's creation, rotation, revocation, and last-used-at.

OAuth 2.0 with PKCE

For apps where end users authenticate (e.g., a customer-facing portal that lets external collaborators access documents):

// 1. Generate code verifier + challenge
const verifier = generateRandomString(64);
const challenge = base64UrlEncode(await sha256(verifier));

// 2. Redirect to authorize
window.location = `https://yourtenant.papyrus.io/oauth/authorize?` + new URLSearchParams({
  response_type: 'code',
  client_id: 'your_client_id',
  redirect_uri: 'https://yourapp.com/callback',
  scope: 'documents:read workflows:read',
  code_challenge: challenge,
  code_challenge_method: 'S256',
  state: stateToken
});

// 3. Exchange code for token (on callback)
const tokenResponse = await fetch('https://yourtenant.papyrus.io/oauth/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: authCode,
    redirect_uri: 'https://yourapp.com/callback',
    client_id: 'your_client_id',
    code_verifier: verifier
  })
});

const { access_token, refresh_token, expires_in } = await tokenResponse.json();

Access tokens are short-lived (1 hour). Refresh tokens rotate on each use.

Key rotation

Best practice: rotate API keys every 90 days. The rotation flow:

  1. Create a new key with the same scopes
  2. Update your backend to use the new key (rolling deploy works)
  3. Once you confirm no traffic on the old key (visible in the admin page), revoke it

A key has no grace period after revocation — calls with the revoked key return 401 immediately.

Security recommendations

  • Never commit keys to git (use env vars or a secret manager)
  • Use the IP allowlist for service-to-service keys
  • Use the smallest scope set that works (don't request documents:write if you only read)
  • Watch the API key audit log for unexpected usage

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.