Skip to content

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