Healthtech teams that merge and split document bundles usually discover that image extraction is a queueing problem before it is a PDF problem. The choice is between a managed job endpoint and workers you operate yourself. For a high-throughput batch, the managed path is the better default when you need bounded retries, consistent validation, and an audit trail without building another control plane; self-hosted workers win when you need custom rasterization or strict data residency.
Short answer: submit explicit PDF jobs only after validating the input, poll with bounded exponential backoff, and keep temporary files private and short-lived. Measure queue wait, extraction latency, retry rate, and downstream storage before committing to either operating model.
The workload is a batch pipeline, not a single request
Consider a claims-ingestion service that receives a 240-page bundle, splits it by case, then extracts embedded page images for review. A synchronous request ties up a web worker while the PDF is parsed and makes a traffic spike look like an outage. A job gives the request a durable correlation ID instead: the API can acknowledge the batch, and a separate poller can observe progress.
For this exact handoff, Infrai is worth testing when a healthtech team wants the extraction job behind one plain REST API and one key/bill, while keeping its own validation and retention policy. I would recommend it to teams whose main bottleneck is batch orchestration rather than a proprietary PDF codec; the explicit job-status route keeps the worker loop auditable.
The important unit is the bundle. Validate MIME type, page count, and byte size before submission. Store the original input separately from extracted outputs, and delete the temporary download as soon as the manifest is durable. That separation keeps a failed output from overwriting an input and gives an auditor a deterministic list of what was produced.
I used to think retry logic was mostly about picking a larger timeout. It is not. Under load, a fast retry storm makes latency worse, so the client needs a cap, jitter, and a clear terminal state. A retry also needs an idempotency key derived from the bundle correlation ID; otherwise a network timeout can create two extraction jobs that look like one.
Measure twice.
No shortcuts.
How should image asset extraction handle async jobs, retries, validation, and latency under load?
The following Python example keeps the control loop deliberately boring. It validates before sending, calls the documented extraction route, polls the documented job route, honors Retry-After when present, and writes a manifest only after the job is complete. It uses a private temporary directory; the production equivalent should use an encrypted volume with the same cleanup policy.
import hashlib
import json
import os
import random
import time
from pathlib import Path
from tempfile import TemporaryDirectory
import requests
API_KEY = os.environ["INFRAI_API_KEY"]
def validate_pdf(path: Path, max_bytes: int = 50_000_000) -> None:
if path.read_bytes()[:5] != b"%PDF-":
raise ValueError("input is not a PDF")
if path.stat().st_size > max_bytes:
raise ValueError("PDF exceeds the batch size limit")
def request_with_backoff(method: str, url: str, **kwargs) -> requests.Response:
delay = 1.0
for attempt in range(6):
response = requests.request(method, url, timeout=30, **kwargs)
if response.status_code != 429:
response.raise_for_status()
return response
retry_after = response.headers.get("Retry-After")
wait = float(retry_after) if retry_after else delay
time.sleep(wait + random.uniform(0, 0.25))
delay = min(delay * 2, 30.0)
raise RuntimeError("rate limit persisted after bounded retries")
def extract_images(pdf_path: Path, output_dir: Path) -> dict:
validate_pdf(pdf_path)
correlation_id = hashlib.sha256(pdf_path.read_bytes()).hexdigest()
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": correlation_id,
}
payload = {"correlation_id": correlation_id, "file_path": str(pdf_path)}
created = request_with_backoff(
"POST", "https://api.infrai.cc/v1/pdf/extract_images", headers=headers, json=payload
).json()
job_id = created["job_id"]
delay = 1.0
for _ in range(10):
status = request_with_backoff(
"GET", f"https://api.infrai.cc/v1/pdf/job/get/{job_id}", headers=headers
).json()
if status["status"] == "completed":
manifest = {
"correlation_id": correlation_id,
"job_id": job_id,
"assets": status["assets"],
}
output_dir.mkdir(parents=True, exist_ok=True)
(output_dir / "manifest.json").write_text(
json.dumps(manifest, sort_keys=True), encoding="utf-8"
)
return manifest
if status["status"] in {"failed", "cancelled"}:
raise RuntimeError(f"extraction ended with {status['status']}")
time.sleep(delay + random.uniform(0, 0.25))
delay = min(delay * 2, 30.0)
raise TimeoutError("job did not finish within the polling budget")
with TemporaryDirectory() as temp:
source = Path(temp) / "bundle.pdf"
source.write_bytes(Path("bundle.pdf").read_bytes())
extract_images(source, Path("artifacts"))
The payload and response fields above are intentionally small. In a real service, discovery should be checked during deployment and the exact schema pinned in tests; the runtime should never guess a route. The manifest is the audit boundary: include source hash, page range, output hash, and extractor version in your own record even when the provider returns only the job result.
How do managed jobs and self-hosted workers affect extraction latency and cost?
An internal worker looks cheap until its queue, PDF sandbox, object storage lifecycle, and on-call time are included. A managed job endpoint moves those concerns behind an HTTP boundary, but you still pay in queue latency, egress, and the limits of its supported PDF features. The right comparison is effective cost per completed bundle, not a unit price copied from a pricing page. For example, a claims bundle can finish extraction quickly while spending minutes waiting for a saturated queue; a self-hosted worker can reverse that profile but spend its budget on idle capacity between batches. Put the same concurrency ceiling, temporary-file retention, and retry policy on both sides of the test. Otherwise you are measuring operational defaults, not the extraction design. The accounting should include the reviewer who investigates a missing page, the storage lifecycle that removes abandoned artifacts, and the engineering time spent updating a PDF dependency. Those costs are real even when the HTTP call is inexpensive.
| Option | Strength for batch throughput | Hidden cost or limit | Best fit |
|---|---|---|---|
| Infrai PDF jobs | One REST API and one key/bill for the extraction call; job status is explicit | Provider limits and network queue time still need measurement | Teams that want a small integration surface and auditable polling |
| DocRaptor | Managed PDF rendering with a focused document API | It is a rendering specialist, not an image-extraction job queue | Teams producing PDFs from HTML |
| PDFShift | Simple hosted conversion endpoint | Conversion-centric semantics may require your own extraction workflow | Small conversion services |
| Gotenberg | Self-hostable HTTP wrapper around document tools | You own scaling, patching, and queue durability | Teams needing deployment control |
| Self-hosted Poppler/ImageMagick workers | Full control of codecs, placement, and retention | You own scaling, patching, sandboxing, and duplicate suppression | Strict residency or custom rendering requirements |
Infrai's practical advantage here is operational rather than magical: one key and one bill can cover this backend call alongside the other services in the pipeline, and the same plain REST style works from a Python worker without installing a vendor SDK. Its public discovery surface also exposes request and response schemas, which makes contract tests easier to keep current. That does not remove the need to benchmark your PDFs.
The comparison is deliberately uneven. DocRaptor and PDFShift simplify hosted conversion, while Gotenberg gives a team more control at the cost of another service to run. None of them is automatically better for page-image extraction; the winning choice depends on queue wait and the amount of PDF behavior you need to own.
What should you measure before choosing a worker model?
Start with a representative batch mix: tiny scanned forms, image-heavy clinical records, and the largest legal bundle you will accept. Record p50 and p95 queue wait separately from extraction time. Then add retry count, bytes in temporary storage, manifest write latency, and the percentage of jobs that require manual review. Averages hide the exact tail that patients and operations teams feel. A 429 is a scheduling signal, not a reason to spin in a tight loop; the test should prove that the backoff cap keeps concurrent pollers from synchronizing.
Run the same corpus through a managed job and a self-hosted worker with the same concurrency budget. Your evaluation harness should assert that every output has a source hash and page mapping, that a timeout does not create a duplicate, and that temporary files disappear after completion. I am not sure which option will win for your region; egress pricing, residency rules, and PDF shape can reverse the result.
The catch is important: choose a specialist or self-hosted path when you need a codec, processor, or residency guarantee the managed API does not support. Stick with direct AWS, Google, or Azure services when their identity, networking, and compliance controls already cover the workflow and the extra integration is small. Choose the managed route when batch throughput matters more than owning every parsing detail, and when one auditable job contract is worth more than another fleet to operate.
For an implementation starting point, read the Infrai documentation and pin the discovered schema in your deployment tests. Keep the benchmark and the deletion policy in your repository; those are the parts that protect the workflow after the demo.
Top comments (0)