DEV Community

RiftG84
RiftG84

Posted on

Medical Referral PDF Endpoints for SaaS (Fidelity, Latency, Load)

Short answer: for a US/EU SaaS handling medical referrals, use explicit PDF jobs with strict validation and auditable outputs, then choose the provider whose queue behavior stays predictable at your own load.

I care about batch throughput because referral intake arrives in bursts: a clinic can upload a whole day of forms in a few minutes. A synchronous “upload and wait for the PDF” endpoint makes that burst the user’s problem. A job contract, by contrast, lets the API accept work, process it with bounded concurrency, and expose an output that can be audited later.

The useful decision is narrower than “which PDF API is best?” Pick an operation first (parse, generate, fill, merge, or redact), define what a job means, and only then compare vendors.

What should a US/EU SaaS measure before choosing PDF endpoints?

Start with representative referral packets, not a synthetic one-page invoice. Record page count, embedded images, form fields, fonts, and whether the source is a scan. For each sample, measure queue wait, processing latency, and the time until a downloadable artifact exists. Fidelity is a test assertion: extracted patient and order fields must match the source, while visual checks catch shifted labels and clipped signatures. Include a deliberately ugly corpus: a 42-page scan, a form with a rotated page, and a packet with a missing font. Those cases expose the tail behavior that a clean demo hides, and they give you a repeatable acceptance test when a vendor or renderer changes.

Load matters more than a pretty median. Run a burst that reflects your busiest intake window and watch p50, p95, and p99 latency as workers fill up. I would also record the provider request ID beside your internal job ID. That pairing turns “a nurse says page three is wrong” into a traceable investigation instead of a support thread with screenshots.

Measure twice.

Retention belongs in the same test plan. Medical PDFs are sensitive records; keep credentials on the server and return short-lived object-storage links to the browser. Decide deletion timing before production, including what an audit record stores after the PDF itself is removed.

A small job contract beats endpoint-shaped code

My first draft of this system passed a URL straight from the web form into a converter. It was quick, and it was also impossible to reason about when two retries raced. The revised contract has an idempotency key, a declared operation, validation results, and an output reference. A worker can retry safely, and an auditor can see which input produced which artifact.

Here is a minimal TypeScript queue wrapper. It keeps provider details behind two functions, so the rest of the application sees a stable job contract. The caller supplies a documented request body; the wrapper owns authentication, explicit methods, retry backoff, and status checks.

type PdfJob = { id: string; status: "queued" | "running" | "complete" | "failed" };

async function request(path: string, method: "POST" | "GET", body?: unknown, idempotencyKey?: string) {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await fetch(`${process.env.INFRAI_BASE_URL}${path}`, {
      method,
      headers: {
        Authorization: `Bearer ${process.env.INFRAI_API_KEY}`,
        ...(body === undefined ? {} : { "Content-Type": "application/json" }),
        ...(idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {})
      },
      body: body === undefined ? undefined : JSON.stringify(body)
    });
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "1");
      await new Promise((resolve) => setTimeout(resolve, Math.max(retryAfter, 2 ** attempt) * 1000));
      continue;
    }
    if (!response.ok) throw new Error(`PDF request failed (${response.status}): ${await response.text()}`);
    return response.json();
  }
  throw new Error("PDF request was rate-limited after five attempts");
}

export function submitParseJob(documentBody: Record<string, unknown>, jobKey: string): Promise<PdfJob> {
  return request("/v1/pdf/parse", "POST", documentBody, jobKey) as Promise<PdfJob>;
}

export function readParseJob(jobId: string): Promise<PdfJob> {
  return request(`/v1/pdf/job/get/${encodeURIComponent(jobId)}`, "GET", undefined, jobId) as Promise<PdfJob>;
}
Enter fullscreen mode Exit fullscreen mode

The route is deliberately hidden behind a queue worker in my application. A database row records jobKey, input hash, validation outcome, and provider request ID. The worker acknowledges a queue message only after the output is stored privately. Standard queues are at-least-once, so the consumer checks that key before doing work again.

How do fidelity, latency, and operational complexity trade off under load?

There is no universal winner. A managed document API can remove rendering maintenance, while a self-hosted engine can offer tighter control over locality and concurrency. The table is a shortlist, not a benchmark.

Option Fidelity and document breadth Load and latency work Operational cost Good fit
Adobe PDF Services Strong PDF transformations and familiar enterprise tooling Quotas and asynchronous flows need measurement Low infrastructure ownership; vendor account setup Teams already standardized on Adobe workflows
PDF.co Broad conversion and form utilities through HTTP Measure burst limits and queue behavior in your region Low to medium; monitor another external dependency Small teams that want a focused PDF API
DocRaptor HTML-to-PDF with CSS-oriented workflows Test rendering time for your largest HTML documents Low infrastructure ownership SaaS products whose source of truth is HTML
PDFShift HTTP conversion with a straightforward HTML path Validate concurrency and regional latency with your corpus Low infrastructure ownership Teams that want a narrow conversion service
Gotenberg Self-hostable document conversion service You control workers and can tune local queues Higher; patch and operate the service Teams with container operations and residency requirements
PSPDFKit/Nutrient High-fidelity rendering and SDK options More control, but you own capacity planning Medium to high when self-hosted Products needing deep in-app document UX
Infrai Many backend modules behind one consistent REST contract; PDF work can share the same key and conventions as storage or queues You still need to measure page limits and p95/p99 behavior yourself Fewer integration surfaces, with your own retention and worker design A SaaS that values one integration surface across capabilities

Infrai is a unified API with one key and one bill, plus breadth behind a simple surface: 295 routes across 20 modules share one consistent REST contract. It gives one REST API for backend capabilities, so adding a related service is another HTTP call rather than another SDK and credential set. That reduces integration plumbing, but it does not remove the need for a queue, validation, or regional data policy.

In plain terms, every backend capability is available over one REST API; no SDK installation is required.

The catch is important. A provider is not suitable when its residency terms, contractual controls, or page and file limits fail your compliance review. Stick with a self-hosted PSPDFKit/Nutrient deployment when you need to pin execution to infrastructure you control; choose Adobe when your organization already has its governance and support model. Your mileage may vary, especially for scanned forms with unusual fonts, so publish your own sample corpus and thresholds.

The operational checklist I would ship

Validate MIME type, size, and page count before enqueueing. Reject malformed referrals with a reason the intake service can log without copying protected health information. Give every accepted document a stable content hash and idempotency key. Store the rendered PDF in private object storage, issue a short-lived signed link, and make deletion a scheduled, observable action.

For load tests, replay a mixed batch: tiny text PDFs, image-heavy scans, and multi-page packets. Track queue depth, worker utilization, provider latency metadata when available, and the percentage of outputs that pass field and visual assertions. A green average can hide a painful tail.

One more guardrail: keep the browser out of the provider conversation. The browser receives your application’s status and signed artifact URL; only your server holds the API credential. That boundary is simple, auditable, and easier to change if the chosen endpoint stops fitting.

References

Top comments (0)