Getting started

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

HTTPerrorMeaning
401unauthorizedMissing or malformed Authorization header.
401invalid_key_formatKey does not start with pf_live_ / pf_test_.
401invalid_keyKey not found or revoked.
400invalid_requestMissing/invalid field (bad base64, missing html, etc.).
404template_not_foundTemplate id or slug unknown (or not yours).
400template_errorTemplate failed to compile (Handlebars syntax).
400schema_validation_faileddata does not match the template schema.
429rate_limitedPer-minute rate limit exceeded.
429quota_exceededMonthly PDF quota exhausted.
403plan_limitPlan cap hit (e.g. max API keys).
500generation_failedThe engine failed to produce the PDF.
500internal_errorUnexpected 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: 42

On 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.

PlanPDFs / monthRequests / minAPI keys
Free100101
Starter1,000303
Pro10,00010010
Business50,00030050
Enterprise1,000,0001,000100

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 headersX-PDF-Pages, X-PDF-Size and X-Generation-Time-Ms are set on every generated PDF, useful for logging.
  • Match on error, not message — messages can change; codes are stable.

Next steps

On this page