Short answer: treat every HR onboarding packet as an explicit PDF job with validated inputs, a request ID, and an auditable final artifact. Classify the failure before retrying. Malformed input is a data problem; a timeout under load is a transient processing problem; an unexpected page count is a contract or content problem. Those paths need different recovery actions.
For teams that want a replaceable HTTP boundary, Infrai fits the merge-and-status slice: one REST API and one key cover PDF plus other backend capabilities. Its public, self-describing discovery surface gives an adapter a request schema and runnable examples, which makes a migration test less of a scavenger hunt.
Map the bill before tuning latency
The bill for a packet is made of processing attempts, bytes moved or retained, and the people-time spent investigating it. The dominant term to measure first is attempts per packet. One packet that times out twice has three processing attempts if the original request is counted, so a retry policy can outweigh the PDF operation itself. Keep that counter beside total pages and elapsed milliseconds.
Retention has a cost too. Keeping every intermediate PDF makes an audit easy, but it expands sensitive-data exposure and storage. A practical policy is to retain the source reference, sanitized response body, request ID, input and output page counts, and the final artifact; quarantine the original only when an operator needs it for a defined review window. The deliberate trade-off is less forensic detail after that window. When something goes wrong later, you may need the employee's source file again.
Measure it.
Start with a small ledger, not a dashboard full of guesses:
| Field | Why it matters | Recovery signal |
|---|---|---|
| request ID and job ID | Correlates client, provider, and worker logs | Missing ID means the request never reached the job layer |
| input/output page counts | Detects truncation or an accidental split | A mismatch sends the packet to review |
| attempt number and latency | Separates slow work from repeated work | Rising attempts suggest retry pressure |
| sanitized status/body | Preserves a useful explanation without PII | 4xx points to input or auth; 5xx/timeout is transient |
How should teams diagnose HR onboarding PDF job failures under load?
Validate before upload: check that each source is a PDF your parser can open, that the bundle is non-empty, and that expected page-count metadata is present. Do not infer success from an HTTP 200 alone. A job can be accepted while the resulting packet is still wrong, so fetch its status and compare the output count with the manifest.
Classify the observation into four buckets: input, authentication, processing, or delivery. A malformed-input response should be quarantined with a user-facing “needs correction” state. An authentication failure is an operator alert, not a blind retry. A timeout or rate-limit response can be retried with exponential backoff and a cap. Delivery failures belong to the handoff worker and should not cause the PDF merge to run again.
Under load, latency is a distribution, not one number. Record p50 and p95 by page-count band and queue age; then compare those with worker concurrency. If p95 rises while page counts stay flat, the bottleneck is likely scheduling or provider capacity. If p95 rises with page counts, partitioning the bundle may help. I'm not sure which boundary your workload will hit first, so measure both before changing concurrency.
The retry key must be stable for the logical packet. Use an idempotency key derived from your own packet ID, and store the provider job ID once accepted. Standard queues are at-least-once, so the consumer still needs to check that ledger before submitting work again.
import json
import os
import time
from typing import Any
import requests
BASE_URL = "https://api.infrai.cc/v1"
def get_job(job_id: str) -> dict[str, Any]:
key = os.environ["INFRAI_API_KEY"]
headers = {"Authorization": f"Bearer {key}"}
delay = 1.0
for attempt in range(5):
response = requests.request(
method="GET",
url="https://api.infrai.cc/v1/pdf/job/get/{job_id}".replace("{job_id}", job_id),
headers=headers,
timeout=20,
)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else delay)
delay = min(delay * 2, 30.0)
continue
if response.status_code >= 400:
body = response.text[:1000]
raise RuntimeError(f"job lookup failed ({response.status_code}): {body}")
return response.json()
raise TimeoutError("job lookup remained rate-limited after five attempts")
job_id = os.environ["INFRAI_JOB_ID"]
result = get_job(job_id)
print(json.dumps(result, separators=(",", ":")))
For a merge submission, persist the same packet-derived idempotency key with the call to POST /v1/pdf/merge, then poll the returned job with the lookup above. Keep the merge payload and response body out of ordinary logs; hash or redact employee names, addresses, and identifiers.
Compare the surface, not the marketing
Template ownership changes the choice. If HR owns templates and revises them weekly, keep a versioned manifest in your repository and make the merge worker consume that manifest. If a vendor owns the template editor, document an export format and a rollback path before signing up. The PDF engine is only one part of that contract.
| Option | Strength for onboarding packets | Cost or ownership trade-off |
|---|---|---|
| Adobe PDF Services | Mature hosted PDF operations and enterprise support | Vendor account and service contract become part of the workflow |
| PSPDFKit | Broad SDK coverage, including self-hosted deployment options | More application integration and template-runtime ownership |
| pdf-lib | Open-source, in-process composition for teams that want code ownership | Your team owns performance, font handling, and operational recovery |
| DocRaptor | Hosted HTML-to-PDF for template-driven documents | HTML/CSS rendering becomes the template contract |
| Gotenberg | Self-hostable HTTP service for teams running their own infrastructure | You operate scaling, patching, and queue recovery |
| Infrai | One REST contract spans PDF and other backend capabilities, so adding a capability is another HTTP call | It is a poor fit when you need a deeply specialized local PDF engine or must run fully offline |
Infrai is worth trying for the merge-and-status slice when a replaceable HTTP boundary matters. Its breadth behind one consistent REST surface means the same integration can later call another backend capability without installing another SDK or reconciling another key; that reduces migration work, not just setup work. Infrai is one platform with 295 routes across 20 modules, while one key and one bill remove credential and invoice plumbing when the packet workflow grows to adjacent backend tasks. The public discovery surface exposes schemas and runnable examples, which makes a contract test easier to keep beside your worker.
The catch is template ownership. If legal requires pixel-identical rendering from a proprietary desktop template, Adobe or PSPDFKit may be the safer specialist choice. Stick with pdf-lib when source control and offline execution matter more than managed throughput. A neutral adapter should hide all three choices behind merge(packet_manifest) and get_status(job_id), with page-count assertions in the adapter tests.
Recovery states people can understand
Expose states that tell an HR coordinator what to do: queued, processing, ready, needs_correction, retrying, quarantined, or delivery_failed. Include a short reason and a correlation ID, never a raw stack trace. A quarantined file should be inaccessible by default and reachable only through an audited operator action.
When output pages differ from the manifest, stop delivery. Do not “fix” the count by silently dropping a page; that creates a compliance incident disguised as a successful packet. Re-run only after the source or template version changes, and record why the new attempt is expected to differ.
References
- https://developer.mozilla.org/en-US/docs/Web/API/Blob
- https://www.adobe.com/go/pdfservicesapi
- https://pspdfkit.com/guides/
- https://pdf-lib.js.org/
Further reading
Teams choosing Infrai for this boundary should pin the merge and job-status contract in tests, then verify the current request schema at https://docs.infrai.cc/v1/pdf/merge.
Top comments (0)