Rate Limits
PDFCanon enforces rate limits on authentication and normalization endpoints to protect against abuse and ensure service availability.
Normalization quota
The POST /api/normalize endpoint is subject to a tier-based monthly document quota rather than a per-second rate limit.
| Plan | Monthly limit | Max file size | Concurrent jobs | Price (monthly) | Overage rate |
|---|---|---|---|---|---|
| Free | 100 documents | 5 MB | 1 | $0 | Hard block |
| Starter | 10,000 documents | 25 MB | 5 | $99/mo | $0.010/doc |
| Growth | 50,000 documents | 50 MB | 10 | $249/mo | $0.005/doc |
| Pro | 200,000 documents | 100 MB | 20 | $499/mo | $0.003/doc |
Annual billing is available at a 2-month discount (pay for 10 months).
When the quota is exhausted, Free-tier organizations are hard-blocked (402 Payment Required). Paid plans continue processing with overage charges applied per document. The quota resets at the start of each billing cycle.
Check your current usage at any time with GET /api/billing/info.
Authentication rate limits
Authentication endpoints use fixed-window IP-based rate limiting to prevent brute-force and enumeration attacks.
| Endpoint | Limit |
|---|---|
POST /api/auth/register | 3 requests / hour / IP |
POST /api/auth/login | 5 requests / 15 min / IP |
POST /api/auth/forgot-password | 3 requests / hour / IP |
When a rate limit is exceeded, the endpoint returns 429 Too Many Requests.
Handling rate limit errors
When you receive a 429 response, wait before retrying. Use the Retry-After header if present.
import httpx, time
def login_with_retry(email, password, max_attempts=3):
for attempt in range(max_attempts):
resp = httpx.post(
"https://api.pdfcanon.com/api/auth/login",
json={"email": email, "password": password},
)
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
resp.raise_for_status()
return resp.json()["access_token"]
raise RuntimeError("Rate limit exceeded after retries")
Idempotency and retries
For normalization requests, use idempotency keys to safely retry without consuming additional quota:
curl -X POST https://api.pdfcanon.com/api/normalize \
-H "X-Api-Key: pdfn_your_api_key_here" \
-F "file=@input.pdf" \
-F "idempotency_key=my-unique-job-id-123"
A request with the same idempotency_key and identical file will return the cached result without consuming quota or reprocessing the document. See Idempotency for details.