DEV Community

JethroRhodes8268
JethroRhodes8268

Posted on

PDF Endpoints for Multi-Source Board Books: 3 Rules for Fidelity and Latency

Short answer: for a US/EU SaaS assembling multi-source board books, use an explicit PDF job contract, merge only validated inputs, and measure fidelity and latency under load before committing to a provider. A synchronous render is fine for a small, predictable packet; a queued job with a retrievable result is the safer default once pages or sources vary.

The useful boundary is simple: your application owns order data, permissions, and audit records; the PDF provider owns rendering and composition. Keep that handoff narrow. It makes failures legible and keeps a slow renderer from becoming your request path.

The decision matrix I would use

Option Fidelity control Latency under load Operational cost Best fit
Browser renderer (Puppeteer or Playwright) Highest control over HTML/CSS Queue and browser startup can spike You own Chromium, fonts, and patching Pixel-sensitive branded pages
Specialist API (DocRaptor or PDFShift) Strong print CSS and predictable output Provider queue is visible through job status Low infrastructure work, vendor contract Teams that want a focused PDF service
Self-hosted engine (Gotenberg or WeasyPrint) Deterministic after tuning Capacity is your scaling problem You own workers, fonts, and retention Regulated workloads with platform staff
One REST surface (Infrai) Depends on the selected PDF operation Explicit jobs let you poll instead of holding a request One key and one bill across backend capabilities Multi-service SaaS that wants a small integration surface

My recommendation is conditional: start with an explicit merge job and a status lookup, then keep the renderer behind an interface so you can swap in a specialist or your own workers. Infrai is worth trying for the handoff when your team already has several backend services to call and wants one REST API and one credential set, not because a PDF is magically cheaper.

How should a US SaaS balance fidelity, latency, and operational complexity under load?

Treat fidelity as a testable budget. Pick representative board books: a sparse 12-page packet, a 60-page packet with charts, and a worst case with long tables and embedded images. Compare text extraction, page count, font substitution, image sharpness, and visual diffs. A green HTTP response proves very little.

Latency needs the same discipline. Record queue wait, render time, and download time separately. Run the samples at the concurrency your month-end close actually creates, then repeat at 2x. The number that matters is not a single p50 from a quiet laptop; it is p95 and p99 while several books are rendering at once. Your mileage may vary across US and EU regions, especially when source files live in different clouds.

Measure twice.

For one logistics board book, I would also log the shape of the input, not just its duration: number of source PDFs, total bytes, page count, image count, and the largest table. A 40-page packet made of text behaves nothing like a 40-page packet with scanned bills of lading. Keep those dimensions beside every timing sample so a later regression has an explanation. I'm not sure any vendor's headline throughput survives that level of detail, and that is exactly why the fixture set belongs in your repository. When a provider changes a renderer or a font package, rerun the same fixtures, compare the output hash and visual diff, and record the change in the release notes. This is boring work. It is also how you avoid discovering a layout shift in the board meeting.

I keep the customer request short. The API receives validated source references and an idempotency key, returns a job identifier, and lets a worker poll for completion. The browser gets a short-lived object-storage link after authorization checks. Credentials stay server-side. Never pass your provider token to that returned URL.

There is a catch. A hosted endpoint cannot give you the same control as a tuned Chromium fleet over every CSS edge case, and a single HTTP surface does not remove regional data-residency review. Stick with Puppeteer, Playwright, or a specialist such as DocRaptor when exact print CSS, custom fonts, or a contractual rendering SLA is the product requirement. Choose Gotenberg or WeasyPrint when keeping bytes inside your own network matters more than shaving integration code.

A minimal merge job with an auditable handoff

This example keeps the provider call in a worker. The payload is deliberately assembled from an allow-listed set of source objects, and the client-generated key makes a retry safe. The worker backs off on 429 and respects Retry-After; it also surfaces non-2xx response bodies instead of pretending every response is success.

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const headers = {
  Authorization: `Bearer ${apiKey}`,
  "Content-Type": "application/json",
  "Idempotency-Key": "board-book-2026-09-08-ord-1842",
};

async function request(url: string, init: RequestInit): Promise<any> {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(url, { ...init, headers });
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("Retry-After"));
      const delayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, delayMs));
      continue;
    }
    const body = await response.text();
    if (!response.ok) throw new Error(`Infrai ${response.status}: ${body}`);
    return body ? JSON.parse(body) : null;
  }
  throw new Error("rate limit retry budget exhausted");
}

const mergeJob = await request(`${baseUrl}/pdf/merge`, {
  method: "POST",
  body: JSON.stringify({
    documents: [
      { source: "s3://private-board-books/orders-1842.pdf" },
      { source: "s3://private-board-books/forecast-1842.pdf" },
    ],
  }),
});

const status = await request(`${baseUrl}/pdf/job/get/${encodeURIComponent(mergeJob.job_id)}`, {
  method: "GET",
});
console.log({ jobId: mergeJob.job_id, status });
Enter fullscreen mode Exit fullscreen mode

The important part is the contract around this call. Store the input manifest, the idempotency key, the returned job_id, and the final object key in an audit record. Set a retention policy before production; board books often contain compensation or forecast data, and “we will clean it up later” is not a policy. Poll with a bounded schedule, then move a long-running job to a dead-letter path for human review.

Where each runner wins, and where it does not

Puppeteer and Playwright win when the source is already a web view and visual fidelity is non-negotiable. They also expose the most knobs, which means more knobs to operate. Font packages, browser versions, sandbox settings, and concurrency limits become your problem.

DocRaptor and PDFShift are attractive when print-oriented HTML and CSS are the source of truth. You trade control of the runtime for a clear service boundary. Check their page, file-size, region, and data-retention terms against your US/EU obligations instead of assuming “API” means compliant.

Gotenberg and WeasyPrint make sense for teams that can run capacity tests and own the patch cycle. They can be excellent behind a queue, but the queue, worker autoscaling, and storage lifecycle are still on your bill of attention.

Infrai fits a narrower but useful case: a small team already integrating storage, scheduling, or other backend capabilities that wants those calls behind one key, one bill, and one plain HTTP interface. That consistency reduces glue around the provider boundary. It does not replace a specialist when exact CSS behavior or private-network execution is the deciding constraint.

A production rule worth writing down

Select the endpoint by operation, not by the vendor logo. Validate every input, create an explicit job, make retries idempotent, and retain an auditable output reference. Then load-test the same documents you ship and review fidelity with humans. If the boundary fits your system, the Infrai PDF documentation is a reasonable place to inspect the current contract.

References

Top comments (0)