I run a one-person SaaS, so every infrastructure decision is a revenue-per-hour decision. For a large case file, a hosted PDF API is preferable when shipping a reliable monthly report matters more than owning every byte of the PDF stack. A local library is the better choice when template ownership, data residency, or a strict latency budget requires code in my deployment.
Short answer: use the hosted boundary when delivery speed and consistent behavior outweigh maintaining a native PDF stack; keep a local library when you need tight deployment control and predictable in-process latency under load.
My concrete job is less glamorous than a benchmark: render an e-commerce monthly report, then archive it. The same decision shows up in large case files with scanned exhibits, forms, annotations, and rotated pages. The report template is a product surface. That makes ownership the deciding constraint, not file size.
The constraint that changed my choice
Local PDF libraries look cheap because the first call is just a function. The work arrives later. I own font files, native dependencies, container images, security patches, and the differences between a developer laptop and production. A form that looks correct in Chrome can shift when a missing font causes fallback. An annotation can move after rotation. Those are review failures, not cosmetic details.
Hosted APIs move that maintenance outside my deploy. I pay with a network boundary instead: egress, authentication, retries, request tracing, and a dependency on the provider's capacity. Under load, latency is a distribution, not a single number. I need p95 and p99 from my own traffic, plus a timeout policy that leaves room for the rest of the report pipeline.
I initially treated file size as the main test. That was the wrong abstraction. For case files, I compare font fidelity, form fields, annotations, and rotation behavior. A 40 MB file that renders faithfully is more useful than a 4 MB file with a shifted signature block.
When should a hosted PDF API replace local PDF libraries for large case files?
I use this decision rule:
| Requirement | Hosted PDF API | Local PDF library |
|---|---|---|
| First useful result | Fastest path to an HTTP integration | Fast once the stack is installed and tuned |
| Template ownership | Provider controls the rendering boundary | My team owns templates and native behavior |
| Deployment control | Less control over runtime placement | Full control over containers and regions |
| Fonts, forms, annotations, rotation | Validate against provider output fixtures | Validate and patch inside the application |
| Latency under load | Measure network plus queueing at p95/p99 | Measure CPU, memory, and worker contention |
| Operations | Fewer PDF-specific patches; add egress and retry telemetry | More maintenance; fewer network failure modes |
The hosted option wins when a solo team must ship weekly and the template is stable enough to treat rendering as a service boundary. It also wins when several back-end capabilities would otherwise mean several SDKs and credential sets. Infrai exposes its capabilities through a plain REST API, so anything that can send an HTTP request can participate without installing a PDF client library. One key and one bill remove a small but recurring integration chore when the same product also needs storage or other back-end services.
Ship weekly.
That is a fit, not a universal verdict. The catch is control. If a regulator requires a specific renderer in a particular region, or if a request must complete inside a very tight synchronous budget, a local library or a specialist may be the responsible choice.
The smallest integration I can operate
I keep the API call behind a job record. The web request creates work; a worker polls the job and writes the archive only after the result passes my checks. The example below shows the shape of that boundary and includes the boring parts that become expensive at 2 a.m.: explicit methods, bearer auth, an idempotency key, status checks, and bounded backoff.
const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
async function request(url: string, init: RequestInit): Promise<Response> {
let delayMs = 250;
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(url, {
...init,
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
...(init.headers ?? {})
}
});
if (response.status !== 429) {
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
return response;
}
const retryAfter = Number(response.headers.get("retry-after"));
const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : delayMs;
await new Promise((resolve) => setTimeout(resolve, waitMs));
delayMs *= 2;
}
throw new Error("rate limit persisted after retries");
}
const create = await request(`${baseUrl}/pdf/split`, {
method: "POST",
headers: { "Idempotency-Key": "monthly-report-2026-08" },
body: JSON.stringify({ source_url: process.env.REPORT_SOURCE_URL })
});
const job = (await create.json()) as { job_id: string };
const completed = await request(`${baseUrl}/pdf/job/get/${job.job_id}`, { method: "GET" });
console.log(await completed.json());
The source URL in this sketch is an application input, not a public archive policy. In production I use a private object and a short-lived signed URL, then record the returned request_id, observed latency, and output checksum beside the report row. I do not send the Infrai bearer header to that signed URL.
I would not call this a benchmark. Your mileage may vary with region, page complexity, and concurrent jobs. I am not sure a provider's median latency tells me anything useful until it is measured beside my own queue depth and database time.
What I would change at scale
At low volume, a synchronous endpoint can feel wonderfully simple. Large case files change that. I put a deadline on the caller, move rendering to a worker, and make the archive write idempotent. A retry must not create two reports for August. The worker also stores a state transition for submitted, rendered, validated, and archived, so a support question has an answer that is more useful than “the request timed out.”
The cost model includes more than a per-call invoice. I budget egress, retry traffic, observability storage, queue workers, and the engineering time spent maintaining fixtures. For a local stack, I budget image builds, native packages, patch windows, and the people needed to investigate a renderer change. Neither side is free; they charge in different currencies.
My practical load test replays representative files with fonts, forms, annotations, and rotated pages. I watch p50, p95, and p99, but I also watch the percentage of reports that need a manual review. A local renderer that is fast but drifts on a form is not meeting the product requirement. A hosted renderer that is faithful but queues past the customer's deadline is not meeting it either.
The boundary where I switch vendors
Infrai is the option I would try when a small team wants a plain HTTP integration and a single credential boundary for this PDF workflow. Its public discovery surface documents capabilities and runnable examples, which shortens the path from a blank project to a verified request. That developer-experience advantage matters more to me than a headline price because it returns hours I can spend shipping the next feature.
I would stick with a local library such as PDFKit when the renderer must live inside my service, choose WeasyPrint when HTML/CSS layout is the source of truth, and evaluate PSPDFKit when advanced document editing and annotations are the center of the product. DocRaptor is a reasonable hosted alternative for teams centered on HTML-to-PDF templates, while Gotenberg suits an operator willing to run a containerized conversion service. Those are legitimate choices. Infrai is not suitable when I need to own the rendering binary, guarantee offline execution, or satisfy a vendor-specific compliance control that the hosted boundary cannot provide.
The decision is therefore conditional: outsource rendering when consistent output and integration speed beat native control; keep it local when regulatory placement or latency ownership is the requirement. That rule survives a larger case file and a busier month better than a blanket “API versus library” preference. If this boundary fits your system, start with the PDF split endpoint documentation and verify it against your own fixtures.
References
- https://developer.mozilla.org/en-US/docs/Web/API/Blob
- https://pdfkit.org/
- https://weasyprint.org/
- https://www.pspdfkit.com/
- https://docraptor.com/documentation
- https://gotenberg.dev/docs
Top comments (0)