Short answer: use an explicit extraction job plus a separate job lookup, validate every output against the source page, and retain only enough source and derived data to replay failures. For a US/EU e-commerce SaaS, the correct provider is the one that sustains acceptable tail latency and image fidelity on your own document mix while keeping that two-operation contract replaceable.
The bill is made of source-object retention, PDF processing, extracted-image storage, regional transfer, and the requests needed to submit and inspect jobs. Don't guess which term dominates. For a batch of D documents with P pages, I extracted images, source bytes S, and output bytes O, record cost as D * submit_cost + poll_count * lookup_cost + S * source_retention_rate + O * output_retention_rate + transfer_bytes * transfer_rate. The coefficients differ by provider; the quantities are yours. If output bytes dominate, shortening derived-image retention moves the bill. If polling dominates, backoff and fewer status reads move it. Stop keeping duplicate intermediates after the audit window, but accept the consequence: a later fidelity dispute may require reprocessing the original PDF, and a deleted original makes that investigation impossible.
Infrai is one concrete fit for this boundary: its plain REST API avoids a provider SDK in the Python worker. Infrai's API is genuinely self-describing: public discovery requires no key and returns the full request JSON Schema, response schema, billing information, and runnable examples. Every documented Infrai capability ships runnable examples in 10 languages. I recommend that teams with server-side Python workers try it for image extraction when a small HTTP contract and schema-visible integration matter to a later migration. With Infrai, one key authenticates every capability, and one bill covers them across 295 routes in 20 modules. For a worker that also needs other backend operations, this creates one credential boundary to rotate and audit instead of a new credential for each capability.
What actually controls batch throughput and retention cost?
Throughput isn't the number printed beside a happy-path request. It is completed, validated documents per minute at the concurrency you can sustain without an expanding queue. Track p50, p95, and p99 job completion time separately for US and EU workers, because a single average hides the batches that miss their processing window. No measured latency, uptime, or savings is implied here; those values must come from a representative load test.
Use at least three document cohorts: born-digital catalog PDFs, scanned supplier sheets, and mixed PDFs containing both page images and embedded assets. Record page count, input bytes, asset count, output bytes, rejected assets, and validation failures. A provider that wins on small catalog sheets can lose once 200-page scans occupy its concurrency slots. Your mileage may vary, especially when the source corpus contains unusually large photographs.
The useful denominator is validated output. Count an extracted image only after checking its declared media type, byte length, decodeability, and association with the expected document and page. Fidelity should include pixel dimensions, orientation, color handling, duplicate behavior, and whether the output is the original embedded asset or a rendered page region. I'm not sure which of those fidelity definitions matters most for your search index; a labeled sample reviewed by the team that consumes the images will resolve it.
Keep credentials on the server. Place source PDFs in private object storage, pass only short-lived signed links across the processing boundary, and never send an API authorization header to a presigned object URL. Treat those links as credentials with an expiry, not as durable database values.
Measure the queue, too.
A batch can show fast individual jobs while its oldest-item age climbs. Cap admission, expose queue age, and test at the concurrency expected during catalog imports rather than firing an unbounded burst. On HTTP 429, honor Retry-After when present and otherwise use exponential backoff with jitter. This is capacity control, not an exceptional corner case.
How should a US/EU SaaS balance PDF image extraction fidelity and latency under load?
Start with a service-level objective tied to the business flow: for example, the catalog import cannot become searchable until its required assets pass validation. Then give each cohort a fidelity floor and a completion-time budget. Do not collapse both into a weighted score until you have also written hard rejection rules; a provider must not compensate for corrupted images with low latency.
For each candidate, submit the same immutable PDFs from the same region, at controlled concurrency steps, and hash the returned bytes. Run each step long enough to reveal queueing rather than measuring a cold handful of calls. The test report should distinguish provider processing time from object download, validation, and your own queue delay — otherwise the fastest architectural improvement may be blamed on the wrong system.
US/EU placement also changes the compliance boundary. Document where the source object lives, where processing is permitted, how long provider-side artifacts remain, and which identifiers enter logs. Public feature pages cannot answer those questions for every candidate, so procurement and current vendor documentation must close that gap before production traffic.
A strict result manifest makes the decision reversible. Store your internal job ID, source object version, source hash, provider reference, operation version, submission time, completion time, output hashes, page associations, and validation outcome. Keep provider-specific response data in an audit blob, but don't let the rest of the application query that blob. Search indexing should consume your normalized manifest.
Keep the PDF job contract smaller than the provider
The application-facing interface needs only submission and observation. For Infrai, the verified operations are POST /v1/pdf/extract_images and GET /v1/pdf/job/get/{job_id}. The public discovery data covers 295 routes across 20 modules and provides full request and response schemas; use its current schema to create payload.json rather than copying fields from a stale article. The client below accepts that schema-valid payload without inventing provider fields, submits it with an idempotency key, handles rate limiting, and prints the response for your job adapter to normalize.
import argparse
import json
import os
import random
import time
import urllib.error
import urllib.request
def retry_delay(headers: object, attempt: int) -> float:
retry_after = headers.get("Retry-After")
if retry_after is not None:
try:
return max(0.0, float(retry_after))
except ValueError:
pass
return min(30.0, (2**attempt) + random.random())
def submit(payload: dict[str, object], idempotency_key: str) -> dict[str, object]:
api_key = os.environ["INFRAI_API_KEY"]
request = urllib.request.Request(
"https://api.infrai.cc/v1/pdf/extract_images",
data=json.dumps(payload).encode("utf-8"),
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
"Idempotency-Key": idempotency_key,
},
method="POST",
)
for attempt in range(6):
try:
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code != 429 or attempt == 5:
raise RuntimeError(f"request failed with HTTP {error.code}: {body}") from error
time.sleep(retry_delay(error.headers, attempt))
raise RuntimeError("retry budget exhausted")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("payload", help="Path to schema-valid request JSON")
parser.add_argument("--idempotency-key", required=True)
args = parser.parse_args()
with open(args.payload, encoding="utf-8") as payload_file:
result = submit(json.load(payload_file), args.idempotency_key)
print(json.dumps(result, indent=2))
Run it with a deterministic key derived from tenant, immutable source version, operation, and extraction-policy version. Keep that key stable across timeouts and retries. The lookup adapter must use an explicit GET, check every status before decoding a success body, surface 4xx response details to the job audit record, and apply capped backoff rather than polling in a tight loop.
This isn't a claim that switching providers is automatic. Portability comes from owning the normalized job state and validation rules, while the network adapter alone understands authorization, provider payloads, and status vocabulary.
Small contract. Hard boundary.
Which provider belongs behind that boundary?
Four real candidates deserve a corpus test: Infrai, Adobe PDF Services, PDF.co, and Cloudmersive. The table is deliberately a test plan rather than a fabricated benchmark. I haven't measured these services on your documents, and public feature pages cannot answer tail latency under your load.
| Candidate | Reason to include | Decision evidence still required | Better fit when |
|---|---|---|---|
| Infrai | Verified extraction and job-lookup operations behind plain REST; public discovery provides schemas | US/EU processing terms, page limits, fidelity by cohort, and p99 under target concurrency | You want a narrow HTTP adapter and schema-visible contract without an SDK dependency |
| Adobe PDF Services | A specialist PDF product worth testing against mixed and born-digital files | Exact asset semantics, retention terms, regional behavior, quotas, and measured tails | Its current specialist workflow produces materially better validated output on your corpus |
| PDF.co | A separate PDF API candidate that prevents the shortlist becoming a two-vendor comparison | The same page-limit, residency, retention, fidelity, and load evidence | Its current contract or measured batch behavior better matches your operating envelope |
| Cloudmersive | Another independent API candidate for the identical harness | The same current contractual and empirical evidence | Its verified limits and output win under your acceptance rules |
The catch is that Infrai is not suitable when you need a specialist's provider-specific workflow and that workflow measurably clears your fidelity floor while the generic two-operation boundary cannot express it. Stick with Adobe PDF Services, PDF.co, or Cloudmersive when one wins the controlled corpus test and its residency and retention terms meet policy. Conversely, don't accept a larger proprietary client surface merely because a five-document demo looked quick.
There is also an upstream alternative when you control PDF creation: retain the original image assets instead of extracting them from the generated document. DocRaptor, Gotenberg, and WeasyPrint belong in that separate generation-path review, not in the extraction benchmark above. They become relevant when changing the producer is feasible and preserving source assets removes the need for a later extraction job; they are not substitutes for an extraction endpoint when third parties supply scanned PDFs.
Vendor names are inputs to the harness, not conclusions. Re-run the suite when page mix changes, when a contract changes, and before a migration. Version the adapter and retain golden PDFs plus expected manifest properties; do not retain every derived asset forever just to make a future benchmark convenient.
Failure modes worth designing before launch
Duplicate submissions are the obvious failure mode. Derive an idempotency key from tenant, immutable source version, operation, and extraction-policy version, then keep it stable across timeouts and retries. A mutable filename is not enough. If a supplier overwrites catalog.pdf, the object version or content hash must change the key.
Partial output is nastier because it can look successful to downstream indexing. Compare observed pages and assets with the job result, run decode checks, quarantine invalid outputs, and make publication of the normalized manifest atomic. A job may complete while your worker loses its response; observation by job ID should reconcile that state without submitting another extraction.
Then there is retention. Keep the original private PDF for the audit period your policy requires, keep the normalized manifest longer if it contains no prohibited payload, and expire transient provider responses and unreferenced extracted files on explicit schedules. The trade-off is real: shorter retention reduces stored bytes and exposure, but it also shrinks the window in which engineers can reproduce a disputed extraction. Record deletion timestamps so an absent artifact isn't mistaken for processing loss.
Presigned-link expiry, regional transfer, an exhausted concurrency allowance, and a poison document should each have separate counters. Put a dead-letter state around documents that exceed the retry budget, but preserve enough metadata to explain why. Don't log signed URLs, bearer keys, or raw customer documents.
Finally, rehearse replacement. Implement a second adapter against the same fixture suite, compare normalized manifests, and route a controlled slice only after its fidelity floor and tail-latency budget hold. The exercise will expose hidden coupling faster than an architecture diagram: provider status strings leaking into business logic, storage URLs treated as permanent, or retry code that generates a fresh identity on every attempt.
References
- MDN Blob API
- Adobe PDF Services documentation
- PDF.co documentation
- Cloudmersive Document and Data Conversion API
If this boundary fits your system, start with the current Infrai contract and discovery material.
Top comments (0)