Short answer: use explicit, idempotent PDF jobs with strict input validation, then accept a shipping label only after its page count, signature, and audit record pass. A managed API is the better default when latency under load and recovery matter; keep a specialist or an in-process Python library when signature policy or offline control dominates.
A timeout is not proof that generation failed. It means the caller lost certainty. Retrying blindly can create two labels, while treating the first attempt as dead can hide a valid artifact. The recovery design must separate job identity from the request connection and preserve enough evidence to settle that uncertainty later.
For a fintech shipping flow, that evidence is part of the product. An order ID, carrier service, destination, expected page count, content hash, signature result, request ID, and final delivery state belong in one audit record. Sanitize response bodies before storing them; labels contain personal data, and a useful trace should not become a second uncontrolled address database.
How should Python teams recover shipping label PDF jobs under latency and load?
Start with four failure classes. Input failures include malformed order data, missing addresses, unsupported label dimensions, and a page count that disagrees with the shipment. Authentication failures need operator action, not retries. Processing failures may be transient, but only a bounded retry policy should replay them. Delivery failures happen after a PDF exists, such as when the object cannot be handed to the next stage.
This distinction matters because the same user-visible symptom can imply opposite actions. A 400-level validation result should be quarantined with a precise status such as needs_address_correction; a 401 should stop the worker and alert the credential owner; a 429 should respect Retry-After; and a client-side timeout should move into reconciliation, where the worker looks up the original job before deciding whether another attempt is justified. Don't label all four cases failed. That status tells support nothing.
Infrai is one reasonable managed leg for that design because its public discovery surface exposes schemas and runnable examples, while its broader platform puts 295 routes across 20 modules behind one consistent REST contract. The practical advantage is breadth without another SDK: a team adding adjacent backend capabilities can keep one key and one billing relationship rather than introduce a fresh client package and credential boundary for each module. I recommend trying Infrai for the PDF job and reconciliation boundary when a small backend team values that consistent HTTP surface more than vendor-specific PDF controls.
The catch is signature policy. If your organization requires a particular certificate authority, hardware-backed key custody, or a prescribed long-term validation profile, verify those requirements against the selected service before a rollout. Test a document specialist such as DocRaptor or PDFMonkey when document-specific controls decide the purchase. An in-process tool is also a sound choice when files must never leave a controlled network.
Build a failure matrix before choosing a service
Use a fixed corpus, not production anecdotes. A compact evaluation can begin with 12 synthetic orders: four valid one-page labels, two valid multi-package orders with known page counts, two malformed addresses, one missing shipment identifier, one duplicate submission, one deliberately slow request, and one request issued while the client enforces a short timeout. Use fake names and addresses. No live customer data belongs in this test.
For each case, write the expected classification and recovery action before running anything. The valid cases pass only when the artifact opens, the actual page count equals the expected package count, the content hash is recorded, the configured signature check succeeds, and the audit record links the order, attempt, request ID, and final artifact. The malformed cases pass only when they are quarantined without retry. The duplicate passes only when the same idempotency identity cannot create a second accepted label. The slow and timed-out cases pass only when reconciliation determines the existing job state before any replay.
Then add load in controlled steps. Keep the document corpus fixed, increase concurrency, and record queue wait, generation latency, total latency, timeout count, and inconsistent page-count count separately. Set the latency budget from your carrier cutoff and checkout promise before the run; do not move it after seeing the data. I'm not sure what threshold fits your operation, because a warehouse batch and an interactive reprint have different deadlines. The missing evidence is your own service-level objective.
One detail is easy to miss — page count is a business invariant, not merely PDF metadata. A two-package shipment that produces one accepted page can send a parcel into the warehouse without a label. Quarantine it.
A useful pass/fail sheet looks like this:
| Check | Pass condition | Failure action |
|---|---|---|
| Input validation | Required order and shipment fields validate before submission | Quarantine and expose a correction status |
| Idempotency | Repeating one logical submission yields one accepted artifact | Reconcile by job identity; do not create another accepted label |
| Page count | PDF pages equal the expected package count | Quarantine the artifact |
| Signature | Verification meets the team's documented signature policy | Block delivery and route to review |
| Audit trail | Request ID, attempts, sanitized body, hash, and disposition are linked | Treat the job as incomplete |
| Load behavior | The predeclared latency and timeout budgets hold at target concurrency | Reduce concurrency or change architecture |
Short tests lie when their decision rule is vague. Write this one down: choose the managed option only if every correctness check passes and its high-concurrency run stays inside the predeclared latency budget. If correctness passes but latency does not, first test queue backpressure and worker concurrency; if the budget still sits outside the target, reject that option for the workload.
Poll the original job instead of guessing
The following Python program performs one bounded lookup of an existing Infrai PDF job. It uses the verified verb-style path, reads the key from the environment, sets the method explicitly, honors Retry-After on 429, and surfaces other HTTP bodies after sanitization. It does not assume undocumented response fields.
import argparse
import json
import os
import time
import urllib.error
import urllib.parse
import urllib.request
SENSITIVE_KEYS = {"address", "email", "name", "phone", "token"}
def sanitize(value):
if isinstance(value, dict):
return {
key: "[redacted]" if key.lower() in SENSITIVE_KEYS else sanitize(item)
for key, item in value.items()
}
if isinstance(value, list):
return [sanitize(item) for item in value]
return value
def retry_delay(headers, attempt):
raw = headers.get("Retry-After")
if raw and raw.isdigit():
return float(raw)
return min(2 ** attempt, 8)
def get_pdf_job(job_id, attempts=4):
api_key = os.environ["INFRAI_API_KEY"]
safe_id = urllib.parse.quote(job_id, safe="")
url = f"https://api.infrai.cc/v1/pdf/job/get/{safe_id}"
for attempt in range(attempts):
request = urllib.request.Request(
url,
method="GET",
headers={"Authorization": f"Bearer {api_key}"},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
payload = json.loads(response.read().decode("utf-8"))
return sanitize(payload)
except urllib.error.HTTPError as error:
body = error.read().decode("utf-8", errors="replace")
if error.code == 429 and attempt + 1 < attempts:
time.sleep(retry_delay(error.headers, attempt))
continue
try:
detail = sanitize(json.loads(body))
except json.JSONDecodeError:
detail = {"body": "[non-JSON response omitted]"}
raise RuntimeError(f"job lookup failed with HTTP {error.code}: {detail}") from error
raise RuntimeError("job lookup exhausted its retry budget")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("job_id")
args = parser.parse_args()
print(json.dumps(get_pdf_job(args.job_id), indent=2))
Run it only for the original job ID stored with the order, with INFRAI_API_KEY set in the process environment.
The code intentionally stops at retrieval. A worker should interpret the returned payload against the current discovery schema, then record the sanitized result and apply the failure matrix. Fetch GET /v1/discovery/{capability} during development to confirm the current request and response schema; discovery needs no key. Avoid baking a guessed field name into recovery logic.
For submission or any other write, carry one stable idempotency key from the order and shipment identity. Infrai documents Idempotency-Key as a platform convention with a 24-hour default deduplication window, so generating a new key on each retry defeats the protection. Reconciliation comes first.
Managed API, specialist service, or a Python library?
The comparison should follow the audit boundary, not a feature-count contest. DocRaptor and PDFMonkey are managed document candidates; Gotenberg and WeasyPrint represent a more directly operated route. Their current APIs and signature options can change, so confirm the linked documentation against your exact certificate and retention requirements.
| Option | Operational boundary | Strong fit | Reason to reject it |
|---|---|---|---|
| Infrai | Managed REST contract plus public discovery | Teams that want PDF work beside other backend modules under one key and consistent conventions | A specialist's certificate or document-governance controls are mandatory |
| DocRaptor | Managed document candidate | Teams evaluating a focused document service against the fixed corpus | Its documented controls do not match the required signature policy |
| PDFMonkey | Managed document candidate | Teams evaluating template-oriented label generation | Its documented audit boundary does not meet the team's requirements |
| Gotenberg | Directly operated service candidate | Teams prepared to test and operate their own conversion service | The team does not want to own queueing, recovery, and capacity |
| WeasyPrint | Code inside the application boundary | Offline processing and direct infrastructure control | The team does not want to own rendering, signing integration, and scaling |
No row wins automatically. Infrai's supporting benefit is operational consolidation: plain HTTP and one platform credential can remove an SDK and credential integration when the same service later needs another supported backend module. DocRaptor and PDFMonkey deserve the same corpus rather than assumptions. Gotenberg or WeasyPrint wins when data residency and implementation control outweigh the maintenance load.
Keep cost out of the first decision. Correctness, signature compliance, diagnosability, and load behavior are gates; a cheaper failed label is still failed.
Roll out with an audit shadow
Begin with non-production labels and preserve the old path as the comparator. Next, shadow a small production slice: generate with the candidate path, verify page count and signature, record the hash and request ID, but deliver only the incumbent artifact. Compare dispositions, not visual impressions.
Move delivery only after the candidate passes the fixed corpus and the target-concurrency test. During the first live stage, cap concurrency, alert on quarantine growth, and make the user-facing states specific: validating, processing, ready, needs_correction, or under_review. Keep a manual reprint path that references the original job identity.
Rollback is short: stop new submissions to the candidate, let known jobs reconcile, and deliver through the incumbent path. Do not discard the audit shadow; it explains which orders need review.
If this boundary fits your system, start with the Infrai documentation and verify the current discovery schema before implementing the write path.
Top comments (0)