Errors & limits
Error codes, quotas and best practices.
Error format
Every error is JSON with a stable machine-readable error code, a human
message, and often a hint telling you how to fix it:
{
"error": "invalid_request",
"message": "The 'html' field is required",
"hint": "Send a JSON body with: {\"html\": \"<h1>Hello</h1>\"}"
}Error codes
| HTTP | error | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or malformed Authorization header. |
| 401 | invalid_key_format | Key does not start with pf_live_ / pf_test_. |
| 401 | invalid_key | Key not found or revoked. |
| 400 | invalid_request | Missing/invalid field (bad base64, missing html, etc.). |
| 404 | template_not_found | Template id or slug unknown (or not yours). |
| 400 | template_error | Template failed to compile (Handlebars syntax). |
| 400 | schema_validation_failed | data does not match the template schema. |
| 429 | rate_limited | Per-minute rate limit exceeded. |
| 429 | quota_exceeded | Monthly PDF quota exhausted. |
| 403 | plan_limit | Plan cap hit (e.g. max API keys). |
| 500 | generation_failed | The engine failed to produce the PDF. |
| 500 | internal_error | Unexpected server error. |
Rate limiting
Live keys are rate-limited per minute (sliding window). Test keys
(pf_test_) are exempt. Each response carries:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42On 429 rate_limited the response also sets Retry-After: 60. Back off and
retry — the JavaScript SDK retries transient
failures automatically.
Monthly quota
Every successful operation (a generation or a /v1/pdf/* tool call) counts
one unit against your monthly quota. When it is exhausted the API returns
429 quota_exceeded until the next cycle or an upgrade.
| Plan | PDFs / month | Requests / min | API keys |
|---|---|---|---|
| Free | 100 | 10 | 1 |
| Starter | 1,000 | 30 | 3 |
| Pro | 10,000 | 100 | 10 |
| Business | 50,000 | 300 | 50 |
| Enterprise | 1,000,000 | 1,000 | 100 |
Free plan watermark
PDFs generated on the Free plan carry a pdfforge watermark. All paid plans produce clean documents.
Track your consumption at any time:
curl https://api.pdf-forge.dev/v1/usage \
-H "Authorization: Bearer $PDFFORGE_API_KEY"{
"plan": "pro",
"monthly_usage": 1284,
"monthly_limit": 10000,
"remaining": 8716,
"daily": [ ... ]
}GET /v1/logs?limit=20&offset=0 returns your recent generation logs
({ "logs": [...], "total": n }, max limit 100).
Best practices
- Handle 429 with backoff — respect
Retry-After, or let the SDK retry. - Use test keys in development — no rate limit, clearly separated from production traffic.
- Check response headers —
X-PDF-Pages,X-PDF-SizeandX-Generation-Time-Msare set on every generated PDF, useful for logging. - Match on
error, notmessage— messages can change; codes are stable.
Next steps
- Authentication — keys and rotation.
- Your first PDF — the happy path.
- API reference — every endpoint at a glance.