Skip to main content

Error response format

Every error response includes at least:
{
  "error": "Human-readable explanation of what went wrong."
}
  • error: A plain-language sentence describing the problem.
  • hint: Optional actionable suggestion. Present on many (but not all) errors.
Rate limit and quota errors additionally include numeric fields so you can react without parsing the message:
{
  "error": "...",
  "hint": "...",
  "limit": 60,
  "remaining": 0,
  "resetAt": 1744000000000
}
resetAt can be:
  • millisecond Unix timestamp (rate limits)
  • ISO timestamp string (renewing quotas)
  • null (non-renewing free-tier quotas)
Quota errors (monthly page exhaustion) also include used:
{
  "error": "Generated page limit reached (1000/1000). ...",
  "hint": "Your quota resets on Thu, 01 May 2026 00:00:00 GMT. Upgrade your plan for higher limits.",
  "used": 1000,
  "limit": 1000,
  "remaining": 0,
  "resetAt": "2026-05-01T00:00:00.000Z"
}

HTTP status codes

StatusMeaning
200Success
202Job accepted (async only)
307Redirect to download URL
400Bad request: fix your request body
401Unauthorized: check your API key
403Forbidden: feature not available on your plan
404Not found: check IDs and account ownership
409Conflict: action not possible in current state
413Payload too large: reduce request size
422Validation error: template or data violates a limit
429Too many requests: rate limited or quota exhausted
500Internal server error: unexpected failure
502Bad gateway: PDF generation service unreachable
504Gateway timeout: generation took too long

Errors by status

HTTP 400: Bad request

Something about the request body is malformed or missing.
ScenarioError message
Request body is not valid JSONRequest body must be valid JSON.
data is an array instead of an objectThe data field must be a JSON object, not an array or primitive.
jsonData does not parse to an objectThe jsonData field must be a valid JSON object, not an array or primitive.
webhookUrl is provided without webhookSecretwebhookSecret is required when webhookUrl is provided.
webhookUrl is not a valid public HTTPS URLWebhook URL must be a valid public HTTPS URL.
Fix: Read the error field first, then hint when present.

HTTP 401: Unauthorized

{
  "error": "Unauthorized. A valid API key or active session is required.",
  "hint": "Pass your API key using the x-api-key header, or Authorization: Bearer <key>."
}
Fix: Add your API key to the request. See Authentication.

HTTP 403: Forbidden

A feature you are trying to use is not available on your current plan. The response body includes an error and hint field. Fix: Upgrade your plan in the dashboard if the hint refers to a paid feature.

HTTP 404: Not found

ScenarioError message
Template ID is wrong or belongs to another accountTemplate not found. It may not exist or may belong to a different account.
Job ID is wrong or belongs to another accountJob not found. It may not exist, may have expired, or may belong to a different account.
Completed job file was deleted or expiredCould not generate a download URL. The file may have been deleted or expired.
Fix: Double-check the ID in your request. Completed job files are retained for 7 days after completion.

HTTP 409: Conflict

Returned when you try to download a job that has not yet completed.
{
  "error": "Job is not ready for download yet. Current status: \"processing\".",
  "hint": "Poll GET /api/v1/jobs/:jobId until status is \"completed\", then download.",
  "status": "processing"
}
Fix: Poll GET /api/v1/jobs/:jobId until status is "completed", then download.

HTTP 413: Payload too large

The request or one of its parts is too large.
What exceeded the limitError message
Total request body (512 KB)Request body exceeds 524288 bytes.
HTML template (200 KB)HTML template exceeds 204800 bytes.
Custom CSS (100 KB)Custom CSS exceeds 102400 bytes.
Data JSON (256 KB)Data JSON exceeds 262144 bytes.
Fix: Reduce the size of the relevant field. See Limits for all thresholds.

HTTP 422: Validation error

The request is well-formed but violates a structural or complexity limit.
ScenarioError message
Data nested more than 12 levelsData depth exceeds 12.
An array has more than 1 000 itemsAt least one array exceeds 1000 items.
Template has more than 2 000 Liquid tokensLiquid token count exceeds 2000.
Inline base64 images exceed 5 MBInline base64 image payload exceeds 5242880 bytes.
More than 100 external asset URLsExternal asset URL count exceeds 100.
Header/footer field longer than 512 charsHeader/Footer fields must be 512 characters or fewer.
Rendered PDF is larger than 20 MBRendered PDF exceeds 20971520 bytes.
Rendered PDF has more than 200 pagesRendered PDF exceeds 200 pages.
Fix: Simplify your template or data. See Limits for all thresholds.

HTTP 429: Too many requests

Common scenarios that use 429:

Rate limit (per-minute window)

You sent too many requests in a short period.
{
  "error": "Generation rate limit exceeded.",
  "hint": "Check the x-ratelimit-reset header for when your rate limit window resets.",
  "limit": 60,
  "remaining": 0,
  "resetAt": 1744000000000
}
Also check the response headers:
x-ratelimit-limit: 60
x-ratelimit-remaining: 0
x-ratelimit-reset: 1744000000000
Fix: Wait until resetAt (millisecond Unix timestamp) before retrying.

Monthly quota exhausted

Your account has used its monthly page allowance.
{
  "error": "Generated page limit reached (1000/1000). Resets on Thu, 01 May 2026 00:00:00 GMT. Upgrade your plan for higher limits.",
  "hint": "Your quota resets on Thu, 01 May 2026 00:00:00 GMT. Upgrade your plan for higher limits.",
  "used": 1000,
  "limit": 1000,
  "remaining": 0,
  "resetAt": "2026-05-01T00:00:00.000Z"
}
Fix: Wait for the quota to reset on the date shown in resetAt, or upgrade your plan.

Active job limit

Too many async jobs are in progress at the same time.
{
  "error": "You have reached the active job limit (10 jobs). Wait for some to complete before submitting new ones.",
  "hint": "Poll your active jobs at GET /api/v1/jobs/:jobId and wait for them to complete, or cancel some with POST /api/v1/jobs/:jobId/cancel.",
  "limit": 10
}
Fix: Cancel queued jobs or wait for running ones to finish.

Render queue saturation (rare)

If the render service is temporarily saturated, sync generation can return:
{
  "error": "Render queue is saturated. Please retry shortly."
}
Fix: Retry with backoff or switch to async generation.

HTTP 500: Internal server error

An unexpected error occurred on the server.
{ "error": "Internal server error." }
Fix: Retry the request. If the problem persists, contact support and include the value of the x-request-id response header if present.

HTTP 502: Service unavailable

The PDF generation service could not be reached.
{ "error": "fetch failed" }
Message text can vary based on the underlying network error. Fix: Retry after a short delay. Async jobs may retry automatically a few times.

HTTP 504: Timeout

A PDF was not produced within the time limit.
{ "error": "PDF rendering timed out." }
Fix: Simplify the template, reduce slow external images or fonts, or split the document. Async jobs may retry automatically a few times.

Handling errors in code

const res = await fetch("https://pdfgorilla.io/api/v1/templates/ID/generate", {
  method: "POST",
  headers: { "x-api-key": process.env.PDFG_API_KEY, "Content-Type": "application/json" },
  body: JSON.stringify({ data }),
});

if (!res.ok) {
  const err = await res.json();

  if (res.status === 401) {
    throw new Error("Check your API key.");
  }

  if (res.status === 404) {
    throw new Error("Template not found. Verify the template ID.");
  }

  if (res.status === 429) {
    if (err.resetAt) {
      // Quota or rate limit: wait until resetAt
      const retryAfter = new Date(err.resetAt);
      console.warn(`Limit hit. Remaining: ${err.remaining ?? "?"}, resets: ${retryAfter}`);
    }
    throw new Error(err.error);
  }

  throw new Error(err.error ?? "Unexpected error.");
}