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:
- Create a new key with the same scopes
- Update your backend to use the new key (rolling deploy works)
- 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:writeif you only read) - Watch the API key audit log for unexpected usage