Auth Endpoints
These endpoints manage account registration, login, session tokens, and password resets. Auth endpoints use standard JSON request/response bodies and do not require an API key — they are used to obtain JWT Bearer tokens for portal management endpoints.
Token lifetimes
| Token | Type | TTL | Where it lives |
|---|---|---|---|
| Access token | JWT (Bearer) | 15 minutes | Returned in the response body of /login and /refresh. Send as Authorization: Bearer <token> on every portal request. |
| Refresh token | Opaque | 30 days (rolling) | Set as an HttpOnly, Secure, SameSite=Lax cookie scoped to .pdfcanon.com. Automatically sent by the browser to /api/auth/refresh. |
Call POST /api/auth/refresh whenever the access token expires (or proactively, e.g. on a 401). The refresh token rotates on every use; old refresh tokens are revoked.
For programmatic, server-to-server access to PDF processing endpoints (/api/normalize, etc.), use API keys - not JWT. See API Keys.
POST /api/auth/register
Register a new organization and owner account. Sends an email verification link to the owner's email address.
Rate limit: 3 requests / hour / IP
Request
POST https://api.pdfcanon.com/api/auth/register
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
orgName | string | ✅ | Display name for the organization |
orgSlug | string | ✅ | URL-safe identifier for the organization |
ownerEmail | string (email) | ✅ | Email address for the owner account |
ownerPassword | string | ✅ | Password (minimum 8 characters) |
captchaToken | string | No | CAPTCHA verification token |
Responses
| Status | Description |
|---|---|
200 | Organization and owner created; verification email sent |
400 | Validation error (invalid email, slug taken, weak password) |
Example
- cURL
- Node.js
- Python
curl -X POST https://api.pdfcanon.com/api/auth/register \
-H "Content-Type: application/json" \
-d '{
"orgName": "Acme Corp",
"orgSlug": "acme",
"ownerEmail": "admin@acme.com",
"ownerPassword": "s3cur3passw0rd"
}'
const response = await fetch("https://api.pdfcanon.com/api/auth/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
orgName: "Acme Corp",
orgSlug: "acme",
ownerEmail: "admin@acme.com",
ownerPassword: "s3cur3passw0rd",
}),
});
if (!response.ok) throw new Error(`Registration failed: ${response.status}`);
import httpx
resp = httpx.post(
"https://api.pdfcanon.com/api/auth/register",
json={
"orgName": "Acme Corp",
"orgSlug": "acme",
"ownerEmail": "admin@acme.com",
"ownerPassword": "s3cur3passw0rd",
},
)
resp.raise_for_status()
POST /api/auth/login
Authenticate with email and password. Returns a short-lived JWT access token and sets an HttpOnly refresh cookie.
Rate limit: 5 requests / 15 min / IP
Request
POST https://api.pdfcanon.com/api/auth/login
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
email | string (email) | ✅ | Account email address |
password | string | ✅ | Account password |
Response (200 OK)
| Field | Type | Description |
|---|---|---|
access_token | string | Short-lived JWT access token |
token_type | string | Always "Bearer" |
expires_in | integer | Token lifetime in seconds |
An HttpOnly refresh_token cookie is also set.
| Status | Description |
|---|---|
200 | Login successful; access token returned |
401 | Invalid email or password |
Example
- cURL
- Node.js
- Python
curl -X POST https://api.pdfcanon.com/api/auth/login \
-H "Content-Type: application/json" \
-c cookies.txt \
-d '{
"email": "admin@acme.com",
"password": "s3cur3passw0rd"
}'
const response = await fetch("https://api.pdfcanon.com/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ email: "admin@acme.com", password: "s3cur3passw0rd" }),
});
if (!response.ok) throw new Error("Login failed");
const { access_token } = await response.json();
import httpx
client = httpx.Client() # use a client to persist cookies
resp = client.post(
"https://api.pdfcanon.com/api/auth/login",
json={"email": "admin@acme.com", "password": "s3cur3passw0rd"},
)
resp.raise_for_status()
access_token = resp.json()["access_token"]
POST /api/auth/refresh
Exchange the HttpOnly refresh cookie for a new short-lived access token. No request body is required — the refresh token is read from the cookie.
Request
POST https://api.pdfcanon.com/api/auth/refresh
The HttpOnly refresh_token cookie must be present (set during login).
Response (200 OK)
Returns the same structure as login: access_token, token_type, expires_in.
| Status | Description |
|---|---|
200 | New access token issued |
401 | Refresh token missing, invalid, or expired |
Example
- cURL
- Node.js
curl -X POST https://api.pdfcanon.com/api/auth/refresh \
-b cookies.txt \
-c cookies.txt
const response = await fetch("https://api.pdfcanon.com/api/auth/refresh", {
method: "POST",
credentials: "include",
});
if (!response.ok) throw new Error("Token refresh failed — re-authenticate");
const { access_token } = await response.json();
POST /api/auth/logout
Invalidate the current session. Clears the refresh cookie.
Request
POST https://api.pdfcanon.com/api/auth/logout
Authorization: Bearer <access_token>
Response
| Status | Description |
|---|---|
200 | Session invalidated |
Example
- cURL
- Node.js
curl -X POST https://api.pdfcanon.com/api/auth/logout \
-H "Authorization: Bearer eyJ..." \
-b cookies.txt
await fetch("https://api.pdfcanon.com/api/auth/logout", {
method: "POST",
headers: { Authorization: `Bearer ${accessToken}` },
credentials: "include",
});
POST /api/auth/forgot-password
Request a password reset email. Always returns 200 to prevent account enumeration — the response does not reveal whether the email address is registered.
Rate limit: 3 requests / hour / IP
Request
POST https://api.pdfcanon.com/api/auth/forgot-password
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
email | string (email) | ✅ | Email address to send the reset link to |
Response
| Status | Description |
|---|---|
200 | Request accepted (email sent if address is registered) |
Example
- cURL
- Node.js
- Python
curl -X POST https://api.pdfcanon.com/api/auth/forgot-password \
-H "Content-Type: application/json" \
-d '{"email": "admin@acme.com"}'
await fetch("https://api.pdfcanon.com/api/auth/forgot-password", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "admin@acme.com" }),
});
httpx.post(
"https://api.pdfcanon.com/api/auth/forgot-password",
json={"email": "admin@acme.com"},
)
POST /api/auth/reset-password
Reset a password using the token delivered in the reset email.
Request
POST https://api.pdfcanon.com/api/auth/reset-password
Content-Type: application/json
| Field | Type | Required | Description |
|---|---|---|---|
token | string | ✅ | Password reset token from the email link |
newPassword | string | ✅ | New password (minimum 8 characters) |
Response
| Status | Description |
|---|---|
200 | Password updated |
400 | Token invalid, expired, or new password too short |
Example
- cURL
- Node.js
- Python
curl -X POST https://api.pdfcanon.com/api/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"token": "abc123resettoken",
"newPassword": "newS3cur3pass!"
}'
const response = await fetch(
"https://api.pdfcanon.com/api/auth/reset-password",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token: "abc123resettoken",
newPassword: "newS3cur3pass!",
}),
},
);
if (!response.ok)
throw new Error("Password reset failed — token may be expired");
resp = httpx.post(
"https://api.pdfcanon.com/api/auth/reset-password",
json={"token": "abc123resettoken", "newPassword": "newS3cur3pass!"},
)
resp.raise_for_status()