Fakturnia Developers Quickstart API reference Polski

PDF rendering queue

GET /documents/{id}/download renders the invoice PDF on the server. Rendering is CPU-bound, so it runs in a small pool of background workers with a bounded queue. Under heavy load the queue can fill up. When that happens the API does not make you wait or time out. It responds immediately with 429 and the error code WAIT_PDF_QUEUE, telling you to retry shortly.

This is different from the quota rate limits. Both return 429, but they mean different things:

WAIT_PDF_QUEUE Quota rate limit
Cause server render pool momentarily saturated your app exceeded its per-endpoint quota
message WAIT_PDF_QUEUE a generic rate-limit message
Lasts seconds (the queue drains fast) until your window resets
What to do retry after Retry-After, no need to slow your overall rate back off, slow down, respect the cooldown

Branch on the message field (the error code), never on the HTTP status alone, so you can tell the two apart.

What you get back

HTTP/1.1 429 Too Many Requests
Retry-After: 5
Content-Type: application/json
{
  "success": false,
  "data": null,
  "message": "WAIT_PDF_QUEUE",
  "executionTime": 2
}

The Retry-After header carries the suggested wait in seconds (typically 5). Honor it: the render slot frees up quickly and a retry almost always succeeds on the next pass.

How to handle it

The download is a read, so retrying is always safe. No Idempotency-Key is needed. Keep retrying while the code is WAIT_PDF_QUEUE, waiting for Retry-After between attempts:

async function downloadInvoicePdf(documentId: string, token: string): Promise<Blob> {
  const url = `https://prod.api.fakturnia.pl/external/v1/documents/${documentId}/download`;

  for (let attempt = 0; attempt < 10; attempt++) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });

    if (res.ok) return res.blob();

    if (res.status === 429) {
      const body = await res.clone().json().catch(() => null);
      // Transient render-queue backpressure: wait the suggested time and retry.
      if (body?.message === 'WAIT_PDF_QUEUE') {
        const retryAfter = Number(res.headers.get('Retry-After')) || 5;
        await new Promise(r => setTimeout(r, retryAfter * 1000));
        continue;
      }
    }

    // Any other status (including quota 429) is a real error for this call.
    throw new Error(`Download failed: ${res.status}`);
  }

  throw new Error('PDF still not ready after several retries');
}

Show the wait to your users. In the Fakturnia web and mobile apps a small "preparing your file" dialog appears on WAIT_PDF_QUEUE and keeps retrying in the background until the PDF arrives. A short, honest progress indicator beats a spinner that looks frozen.

Why a queue instead of just waiting

Holding the connection open while a long render finishes would tie up server resources and risk client timeouts under load. A bounded queue with fast 429 feedback keeps the API responsive for everyone: the rest of the API never slows down because a burst of downloads is in progress, and your client gets a clear, retryable signal instead of an opaque timeout. See Errors for the full error envelope and Rate limits for quota behavior.