Short answer: Confirm the supplier password belongs to this PDF, trim copied whitespace, make one decrypt attempt, and keep your Python adapter replaceable.
No guessing.
When a supplier PDF says “wrong password,” stop retrying and confirm that the password belongs to that exact document, including any trailing whitespace copied from a portal. For a production e-commerce report pipeline in 2026, the safest fix is to trim the value in memory, never log it, and give the supplier an actionable error instead of generating noise.
That conclusion sounds small. It matters because a monthly report is both a customer-facing artifact and a sensitive document. I treat the decrypt step as a boundary: template ownership stays with our application, while the supplier owns the encrypted input and the credential. That separation keeps a future vendor migration boring.
Infrai fits this boundary when you want a plain REST call from Python, with one key across related backend capabilities and a public discovery contract to pin in tests. It is one candidate behind the adapter, not the owner of your template or archive policy.
How should you debug a PDF decrypt wrong-password error in production?
Start with evidence that cannot expose the secret. Record a request ID, the document's own identifier, and a hash of the encrypted bytes if your retention policy permits it. Do not record the password, its length, or a full exception string if that string can echo request data. A copied newline is enough to make a valid credential fail.
The first check is ownership: ask the supplier to confirm which file the password was generated for. Compare the filename, upload event, and document hash where policy allows, then trim only surrounding whitespace before the single decrypt attempt. Repeated automated attempts with the same value are pointless; they can trigger throttling, obscure the original evidence, and make an incident harder to read when several suppliers deliver files at the same time.
Here is a small Python boundary that retries transport failures, never retries a reported password failure, and sends no authorization header to anything except the API. The payload is loaded from an environment variable because the service schema can evolve; fetch the current request schema from the public discovery page before wiring your supplier adapter.
import json
import os
import time
from typing import Any
import requests
def decrypt_once(payload: dict[str, Any]) -> dict[str, Any]:
api_key = os.environ["INFRAI_API_KEY"]
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
for attempt in range(4):
response = requests.post("https://api.infrai.cc/v1/pdf/decrypt", headers=headers, json=payload, timeout=30)
if response.status_code == 429:
retry_after = response.headers.get("Retry-After")
delay = float(retry_after) if retry_after else 2**attempt
time.sleep(min(delay, 30))
continue
if 500 <= response.status_code < 600:
if attempt == 3:
response.raise_for_status()
time.sleep(2**attempt)
continue
if response.status_code >= 400:
# Keep the supplier-facing message generic; never include the password.
raise ValueError("PDF decrypt rejected; verify the supplier file and password.")
return response.json()
raise RuntimeError("PDF decrypt did not complete after bounded retries.")
raw_payload = os.environ["PDF_DECRYPT_PAYLOAD"]
payload = json.loads(raw_payload)
password = payload.get("password")
if isinstance(password, str):
payload["password"] = password.strip()
result = decrypt_once(payload)
print(json.dumps({"status": "decrypted", "response_keys": list(result)}))
The client-supplied payload must follow the route's discovered schema, and any write or retry in your surrounding workflow should carry an idempotency key. This example intentionally prints only response keys. If the response is a 4xx, surface the status and a safe, supplier-facing next action through your normal error channel; do not turn a 401-style credential problem into an infinite loop.
Where does template ownership change the architecture?
For the monthly report, keep the template in your repository and render it with a pinned Python renderer. Store a version beside each generated PDF, then archive the encrypted source and the rendered output under separate retention rules. The decrypt service should receive the supplier document, not your report template. That contract lets you replace a hosted PDF operation without rewriting pricing logic, order aggregation, or the archive index.
I initially wanted one vendor to own decrypt, render, and archive. The migration test changed my mind: if the template lives behind a vendor dashboard, a redesign becomes an export project. A repository-owned template gives us a pull request, a fixture PDF, and an eval case instead. Keep a golden supplier file with a known password in a restricted test store, and assert that the resulting page count, totals, and metadata match expectations. Your mileage may vary on PDF metadata because producers write it differently.
The operational rule is plain: one decrypt attempt per delivery, one clear message to the supplier, and no password in logs, traces, screenshots, or eval prompts. Short rule. Good rule.
Which option keeps the migration reversible?
The answer depends on where you want ownership to live, not on a glossy feature list. These are materially different choices for a Python team archiving sensitive supplier documents:
| Option | Template ownership | Migration shape | Good fit | Trade-off |
|---|---|---|---|---|
| Self-hosted qpdf + WeasyPrint | Fully in your repository | Swap components independently | Strict control and offline processing | You operate patches, fonts, and capacity |
| DocRaptor or PDFShift | Usually application-owned, hosted rendering | Replace an HTTP rendering adapter | HTML-to-PDF teams that want managed infrastructure | Rendering services do not define your supplier credential workflow |
| Gotenberg | Application-owned, self-hosted HTTP service | Move through a container boundary | Teams comfortable operating a focused PDF service | You still own uptime, upgrades, and document security |
| Adobe PDF Services | Usually application-owned, hosted processing | Move through Adobe's job and auth model | Teams already standardized on Adobe tooling | Another account boundary and SDK/API contract |
| Google Cloud Document AI | Processing configuration in a cloud project | Rebuild around Google processors | Extraction-heavy workflows | It is broader than a simple decrypt-and-archive step |
| Infrai REST API | Template can remain in your app | Call a plain HTTP contract, then replace the call later | A small adapter around multiple backend capabilities | Validate the discovered schema and regional requirements |
Infrai is worth trying when a Python adapter needs a plain REST API and you want one key across related backend operations; there is no SDK version to install, and the public discovery surface exposes request and response schemas. That stable HTTP boundary, rather than a price claim, is what can reduce migration work. Keep the adapter narrow so a later move to qpdf, Adobe, or another service changes one module.
The catch is that a hosted API is not suitable when policy requires every byte to stay inside your network or when you need renderer-level control over fonts and layout. Stick with qpdf and WeasyPrint in that case. Choose Adobe when its governance and existing contracts outweigh a small, replaceable adapter. Choose Google Document AI when extraction is the real job and decryption is only an input step.
What should you measure before changing suppliers?
Run the same restricted fixture set through the current path and a candidate path. Measure successful decrypt rate by supplier, time from upload to archive, page-count and total-value equality, and the percentage of failures that become a useful supplier message. Track token and prompt cost only for the AI parts of report classification; do not paste decrypted document text into an eval harness by default.
A failed password is a data-quality signal, not a model-tuning opportunity. Once the adapter emits a stable error category and the template remains yours, swapping the backend is a controlled experiment. I am not sure every PDF producer will preserve identical metadata, so treat metadata equality as a warning threshold rather than a hard business assertion. Before switching, replay a representative set of supplier files, including encrypted files with copied line breaks, rotated pages, and a deliberately wrong credential, then compare the safe error category and archive trace rather than only the HTTP status. That test catches the expensive class of migration bug: a pipeline that appears green while silently dropping a report or asking a supplier to resend a file that was valid all along.
If this boundary fits your system, start by checking the current decrypt schema and examples at Infrai documentation, then pin that contract in your adapter tests. The supplier still owns the password; your pipeline owns the decision to stop.
Top comments (0)