OAuth 2.1
Overview
Note
This section is for developers building third-party applications that need to access PDFlys on behalf of other users. If you're just using the API for your own projects, API key authentication is all you need.
The PDFlys authorization server supports OAuth 2.1 with:
- Dynamic Client Registration (RFC 7591)
- Authorization Code flow with PKCE (required)
- Refresh token rotation
- Token revocation (RFC 7009)
- JWKS discovery
Discovery
Server Metadata
GET /.well-known/oauth-authorization-server
Returns the full OAuth server configuration including supported endpoints, scopes, and grant types.
JWKS (JSON Web Key Set)
GET /oauth/jwks
Returns the public key used to verify access tokens.
Client Registration
Register your application to get a client_id.
POST /oauth/register
Content-Type: application/json
Request
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
client_name |
string | Yes | — | Your application name (1–255 chars) |
redirect_uris |
string[] | Yes | — | HTTPS callback URLs (http://localhost allowed for dev) |
token_endpoint_auth_method |
string | No | none |
none (public) or client_secret_post (confidential) |
scope |
string | No | — | Space-separated requested scopes |
logo_uri |
string | No | — | HTTPS URL to your app logo |
Example
curl -X POST https://api.pdflys.com/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My PDF Tool",
"redirect_uris": ["https://myapp.com/callback"],
"token_endpoint_auth_method": "none",
"scope": "pdflys:read pdflys:write"
}'
Response (201)
{
"client_id": "pdflys_client_abc123def456...",
"client_name": "My PDF Tool",
"redirect_uris": ["https://myapp.com/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}
For confidential clients (client_secret_post), the response also includes a client_secret.
Note
Re-registering the same client_name + redirect_uris + token_endpoint_auth_method returns the existing client_id instead of creating a duplicate.
Authorization Flow
Step 1: Redirect to Authorization
Redirect the user's browser to:
GET /oauth/authorize
| Parameter | Type | Required | Description |
|---|---|---|---|
response_type |
string | Yes | Must be code |
client_id |
string | Yes | Your client ID |
redirect_uri |
string | Yes | Must exactly match a registered URI |
code_challenge |
string | Yes | Base64url-encoded SHA-256 hash of the code verifier |
code_challenge_method |
string | Yes | Must be S256 |
state |
string | Recommended | Opaque value echoed back — strongly recommended to prevent CSRF attacks |
scope |
string | No | Space-separated scopes |
Example URL
https://api.pdflys.com/oauth/authorize?
response_type=code&
client_id=pdflys_client_abc123&
redirect_uri=https://myapp.com/callback&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256&
state=random-csrf-token&
scope=pdflys:read+pdflys:write
The user approves or denies on the PDFlys consent page, then is redirected back to your redirect_uri:
https://myapp.com/callback?code=AUTH_CODE&state=random-csrf-token
Authorization codes expire after 10 minutes and are single-use.
Available Scopes
| Scope | Description |
|---|---|
pdflys:read |
Read access to PDF operations and reports |
pdflys:write |
Write access to PDF operations |
pdflys:admin |
Administrative access |
mcp:tools |
Access to MCP tool endpoints |
account:read |
Read account information |
Step 2: Exchange Code for Tokens
POST /oauth/token
Content-Type: application/x-www-form-urlencoded
| Parameter | Type | Required | Description |
|---|---|---|---|
grant_type |
string | Yes | authorization_code |
code |
string | Yes | The authorization code |
redirect_uri |
string | Yes | Must match the original request |
client_id |
string | Yes | Your client ID |
code_verifier |
string | Yes | The original PKCE code verifier (43–128 chars) |
client_secret |
string | If confidential | For client_secret_post clients |
curl -X POST https://api.pdflys.com/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=AUTH_CODE" \
-d "redirect_uri=https://myapp.com/callback" \
-d "client_id=pdflys_client_abc123" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
Response
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rt_abc123...",
"scope": "pdflys:read pdflys:write"
}
Refreshing Tokens
POST /oauth/token
| Parameter | Type | Required | Description |
|---|---|---|---|
grant_type |
string | Yes | refresh_token |
refresh_token |
string | Yes | The current refresh token |
client_id |
string | Yes | Your client ID |
client_secret |
string | If confidential | For client_secret_post clients |
Warning
Refresh token rotation is enforced. Each refresh returns a new refresh token and invalidates the old one. Always store the latest token.
Token Revocation
Revoke an access token or refresh token (RFC 7009).
POST /oauth/revoke
Content-Type: application/x-www-form-urlencoded
| Parameter | Type | Required | Description |
|---|---|---|---|
token |
string | Yes | The token to revoke |
token_type_hint |
string | No | access_token or refresh_token |
client_id |
string | Yes | Your client ID |
Always returns 200 OK regardless of whether the token was found.
PKCE Implementation
All OAuth flows require PKCE with S256:
import crypto from "crypto";
// 1. Generate a random code verifier (43-128 chars)
const codeVerifier = crypto.randomBytes(32).toString("base64url");
// 2. Create the code challenge (SHA-256 hash, base64url-encoded)
const codeChallenge = crypto
.createHash("sha256")
.update(codeVerifier)
.digest("base64url");
// 3. Use codeChallenge in authorization request
// 4. Use codeVerifier in token exchange
Error Responses
OAuth endpoints return errors in the standard OAuth format:
{
"error": "invalid_grant",
"error_description": "Authorization code has expired"
}
| Error Code | Description |
|---|---|
invalid_request |
Malformed request parameters |
invalid_client_metadata |
Registration validation failure |
invalid_grant |
Invalid, expired, or already-used code/token |
invalid_scope |
Requested scope is invalid or not allowed |
access_denied |
User denied the authorization request |