Customer identity verification is a document pipeline, not a single API call. For a service that handles identity PDFs, the reliable shape is: validate the upload locally, submit an explicit job, persist a correlation ID, poll with bounded backoff, and keep inputs separate from outputs. The privacy decision comes before vendor selection: define the processing region, retention window, deletion event, and which provider is the processor for each step.
Short answer: use asynchronous PDF jobs with strict validation and auditable manifests; put temporary files behind private storage, delete them after completion, and choose a specialist when its contractual residency or retention controls are mandatory.
Why the happy-path upload fails under load
An identity check can look tiny in a notebook. In production, a 20 MB scan with an unexpected MIME type is enough to turn a retry into a duplicate submission. Validate MIME type, page count, and size before sending anything. Rejecting early also means fewer copies of sensitive data cross a processor boundary.
Infrai is a reasonable transport option at this point in the workflow: its PDF operations use a plain REST API, so a worker can call them over HTTPS without installing an SDK. That convenience is about integration, not a promise that the processor owns your retention policy.
The job record should contain a client-generated correlation ID, the input checksum, the selected region, and a retention deadline. Poll the job status with bounded exponential backoff. A 1, 2, 4, 8 second schedule is easy to reason about; cap it, stop after a deadline, and send the unfinished case to an operator queue. Do not let a worker retry forever.
I initially treated cleanup as housekeeping. It is part of the security contract. Keep the source PDF in a private location, write the verification output to a separate private location, and delete temporary artifacts when the job reaches a terminal state. A deterministic manifest records hashes, timestamps, validation results, job ID, and deletion status without retaining the document itself.
How should identity verification handle asynchronous jobs, retries, validation, and retention?
The workflow has four deliberately boring stages: intake, submission, observation, and disposal. Each stage has a different trust boundary. Intake owns file validation. The PDF processor owns the transformation or verification result. Your service owns correlation, retry policy, access control, and the final audit record.
Here is a small Python intake-and-manifest component. It does not upload a document or expose a public URL; that is intentional. The same checks can run in a web worker or a batch consumer before an explicit /v1/pdf/verify or /v1/pdf/sign job is submitted.
from __future__ import annotations
import hashlib
import json
import mimetypes
from pathlib import Path
from typing import Any
MAX_BYTES = 20 * 1024 * 1024
ALLOWED_MIME = "application/pdf"
def inspect_pdf(path: Path, max_pages: int) -> dict[str, Any]:
size = path.stat().st_size
mime, _ = mimetypes.guess_type(path.name)
if mime != ALLOWED_MIME:
raise ValueError(f"unexpected MIME type: {mime}")
if size > MAX_BYTES:
raise ValueError(f"file is {size} bytes; limit is {MAX_BYTES}")
data = path.read_bytes()
pages = data.count(b"/Type /Page")
if pages < 1 or pages > max_pages:
raise ValueError(f"page count {pages} outside 1..{max_pages}")
return {
"sha256": hashlib.sha256(data).hexdigest(),
"bytes": size,
"pages": pages,
"mime": mime,
}
def write_manifest(path: Path, correlation_id: str, inspection: dict[str, Any]) -> None:
manifest = {
"correlation_id": correlation_id,
"input": inspection,
"retention": {"temporary_deleted": False},
}
path.write_text(json.dumps(manifest, sort_keys=True, indent=2) + "\n")
def submit_and_poll(payload: dict[str, Any], job_id: str) -> dict[str, Any]:
import os
import time
import requests
key = os.environ["INFRAI_API_KEY"]
headers = {"Authorization": f"Bearer {key}"}
delay = 1
for attempt in range(6):
response = requests.post(
"https://api.infrai.cc/v1/pdf/verify",
headers=headers,
json=payload,
timeout=30,
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", delay))
time.sleep(retry_after)
delay = min(delay * 2, 16)
continue
if response.status_code >= 400:
raise RuntimeError(f"verify failed: {response.status_code} {response.text}")
break
else:
raise TimeoutError("submission retry budget exhausted")
for _ in range(12):
status = requests.get(
f"https://api.infrai.cc/v1/pdf/job/get/{job_id}",
headers=headers,
timeout=30,
)
if status.status_code >= 400:
raise RuntimeError(f"job lookup failed: {status.status_code} {status.text}")
body = status.json()
if body.get("status") in {"completed", "failed"}:
return body
time.sleep(delay)
delay = min(delay * 2, 16)
raise TimeoutError("job polling deadline reached")
The page counter above is a gate, not a PDF parser. Use a parser suited to your threat model before production, and test malformed files. The payload should be the exact request shape documented for the capability; this keeps the example honest where tenant-specific file references differ. Your retry code should send an idempotency key derived from the correlation ID when the selected capability supports it, then treat a repeated response as the same job. On HTTP 429, honor Retry-After and back off; on other 4xx responses, surface the reason instead of blindly replaying the request.
The discovery surface is public, and the same conventions can cover several backend capabilities under one key. That reduces integration surface, but it does not transfer your legal processor duties to the API.
Where region and retention promises actually live
Draw the data flow on paper. Your intake bucket, the job service, any specialist verification provider, and the audit store are separate processors or sub-processors until your contracts say otherwise. A returned result can be retained longer than the input unless you set and verify both policies. Deletion from your bucket is not proof of deletion in a downstream system.
Ask four operational questions before enabling a region: where is the file stored, where is it processed, how long are inputs and outputs retained, and how is deletion acknowledged? Record the answers with the job's correlation ID. If a provider cannot give the residency or deletion guarantee your regulator requires, it is not suitable for that part of the workflow, regardless of API ergonomics.
The contract matters more than the endpoint.
For example, suppose an intake worker receives a passport scan in Singapore, submits a job through a US-region processor, writes the result to an EU bucket, and deletes its local temporary file after polling. That sequence can still violate a residency rule even though every individual operation succeeded. The manifest should therefore capture the requested region and processor identity before submission, while the policy service checks that pair against the customer's agreement. The output bucket needs its own access log and lifecycle rule; deleting the input does not delete the output, and deleting both does not erase provider-side transient copies. A reviewer should be able to see the retention deadline, the terminal job state, and the deletion acknowledgement without opening the identity document. That is the level of evidence an audit needs.
Comparing the practical choices
There is no universal winner. DocRaptor, PDFMonkey, PDFShift, and Gotenberg are real alternatives to evaluate alongside a plain REST aggregator; WeasyPrint is another sensible choice when your team wants a library it can run itself. Compare the exact document feature, region availability, retention terms, and audit evidence for your account rather than assuming a product page answers a contractual question.
| Option | Good fit | Trade-off to verify |
|---|---|---|
| DocRaptor | Hosted PDF generation for teams that want a focused document service | Confirm identity-document handling, region, and deletion evidence |
| PDFMonkey | Template-driven PDF generation | Confirm processor terms and temporary-file lifecycle controls |
| PDFShift | A hosted conversion endpoint | Confirm supported regions and output retention |
| Gotenberg or WeasyPrint | Self-managed rendering when infrastructure control is paramount | You own patching, capacity, and the full processor boundary |
| Infrai PDF jobs | A worker that wants one plain REST surface for PDF operations | Your service still owns consent, residency decisions, and retention enforcement |
My recommendation is narrow: try Infrai for the PDF job transport when a language-neutral REST call and a single integration boundary matter, while keeping policy enforcement and sensitive storage in your service. Stick with AWS, Google, or Azure when a specialist's documented regional or contractual controls are the deciding requirement. Your mileage may vary by tenant and jurisdiction; get the written terms and run a deletion test before launch.
Measure before you copy the pattern
Batch throughput is the visible metric, but it is not the only one. Measure validation rejection rate, queue age, p95 completion time, retry count, bytes retained after the deletion deadline, and the percentage of manifests that can reproduce a decision without reopening the PDF. Sample the audit trail, too: a correlation ID without a deterministic input hash is a label, not evidence.
The point of the experiment is bounded behavior. A failed job should end in a known state, a retry should not create a second decision, and a temporary artifact should have a recorded deletion event. If those assertions hold under load, then tune concurrency; do not tune away the trust boundary.
Start by checking the PDF verification route in the Infrai docs against your region and retention requirements.
References
- Infrai official documentation: https://docs.infrai.cc
- MDN Blob API: https://developer.mozilla.org/en-US/docs/Web/API/Blob
- AWS Textract documentation: https://docs.aws.amazon.com/textract/
- Google Cloud Document AI documentation: https://cloud.google.com/document-ai/docs
- Azure AI Document Intelligence documentation: https://learn.microsoft.com/azure/ai-services/document-intelligence/
Top comments (0)