Skip to main content

Error Handling

PDFCanon uses structured error responses and a defined failure taxonomy to make errors actionable.

Error response format

All API errors return a JSON body with an error object containing specific fields:

{
"error": {
"type": "QUOTA_EXCEEDED",
"code": "QUOTA_EXCEEDED",
"message": "Your organization has used all 500 normalizations for this billing period.",
"plan_tier": "Starter",
"monthly_limit": 500,
"current_usage": 500,
"upgrade_url": "/portal/billing"
}
}

HTTP status codes

StatusMeaning
200Success
400Invalid request (malformed PDF, missing headers)
401Missing or invalid API key
403Insufficient permissions
409Idempotency key conflict
413Payload too large
422Unprocessable PDF (permanently failed normalization)
429Rate limit or quota exceeded
500Internal server error
503Service temporarily unavailable

Failure taxonomy

Normalization failures fall into two categories: permanent and transient.

Permanent failures (do not retry)

Error typeDescription
ENCRYPTED_UNSUPPORTEDPDF is password-encrypted and cannot be decrypted
CORRUPT_UNRECOVERABLEPDF structure is too corrupted to repair
ACTIVE_CONTENT_BLOCKEDDocument contains active content that cannot be safely removed
SIGNATURE_PRESERVATION_FAILEDDigital signature could not be preserved per policy

Transient failures (safe to retry)

Error typeDescription
PROCESSING_TIMEOUTJob exceeded the processing time limit
WORKER_UNAVAILABLENo worker was available to process the job
STORAGE_ERRORTemporary storage failure

Warning codes

Warnings do not prevent normalization but indicate that the output may differ from expectations:

Warning codeDescription
NON_EMBEDDED_FONTA font referenced in the document is not embedded
BROKEN_OBJECT_REFERENCEOne or more object references in the PDF were broken and repaired
LARGE_DOCUMENTDocument exceeds the recommended size threshold for optimal processing
COMPLEX_FORMAcroForm structure is complex and may have been partially flattened
MULTIPLE_REVISIONSDocument contained multiple incremental update revisions
CORRUPT_STREAMOne or more content streams were corrupt and were reconstructed

Retry strategy

For transient failures, use exponential backoff with jitter. Always use idempotency keys when retrying:

import time, random, httpx

def normalize_with_retry(pdf_bytes, api_key, idempotency_key, max_attempts=4):
for attempt in range(max_attempts):
resp = httpx.post(
"https://api.pdfcanon.com/api/normalize",
content=pdf_bytes,
headers={
"X-Api-Key": api_key,
"Idempotency-Key": idempotency_key,
"Content-Type": "application/pdf",
},
)
if resp.status_code == 200:
return resp.content
if resp.status_code in (422, 400):
raise ValueError(f"Permanent failure: {resp.json()}")
if attempt < max_attempts - 1:
delay = min(0.5 * (2 ** attempt) + random.uniform(0, 0.5), 30)
time.sleep(delay)
raise RuntimeError("Max retries exceeded")

Next steps