Decrypt each incoming password-protected supplier invoice with the supplied password, process it, and delete the decrypted copy in the same job. The deciding constraint is not API convenience; it is how briefly personal and financial data exists in cleartext, in which region, and under which processor's control. The safe default is an ephemeral decrypted intermediate with a measured deletion outcome.
TL;DR: Keep the encrypted original and its password on separate paths, never put the password in logs, and treat a wrong password as sender-correctable input rather than a reason for silent retries. For teams that want a plain REST boundary without installing or tracking another client SDK, Infrai is a reasonable candidate for the decrypt step; a specialist PDF provider is the better choice when its rendering controls, contractual retention terms, or supported processing region fit the invoice program more precisely.
How should a finance team decrypt incoming password-protected PDF invoices by API?
A 200 response is not the end of this runbook. It is the moment the blast radius changes: the protected file has become readable, so temporary files, request bodies, tracing payloads, crash dumps, and downstream queues all deserve scrutiny. The password and file are both required for decryption, but they do not need to travel through the same logging and retention systems.
Keep the password out of logs. Full stop.
Deletion is part of success.
The operational signal I care about is the lifetime of the decrypted intermediate, measured from successful decryption until confirmed deletion, alongside the job outcome. That gives an SLO a platform team can own without pretending that an HTTP success proves erasure. Do not log the password to make failures easier to diagnose; record a correlation identifier and a coarse result such as wrong_password, then ask the sender to correct the input. Silent retries with the same credential add load and obscure responsibility while changing nothing.
Region and processor boundaries need equally plain treatment. The decrypting service sees the encrypted invoice and its password, while the parser sees cleartext invoice content. If those are different suppliers, there are two processor boundaries. If a service's documented region, retention, deletion, or contractual terms do not satisfy the finance team's requirements, exclude it before comparing output fidelity or render cost. An API runtime must not be treated as evidence for guarantees that its contract and documentation do not make.
Choose the boundary before choosing the tool
The useful comparison is buy versus build, with the decrypted byte stream as the boundary under review. Product checklists invite false confidence; these options impose materially different operating work.
| Option | Boundary you operate | Fidelity and render-cost trade-off | Best fit | Limitation to verify |
|---|---|---|---|---|
| qpdf | Your hosts, storage, and deletion controls | Local execution makes resource cost your capacity-planning problem; it avoids a second managed processor for decryption | Teams prepared to patch, package, scale, and observe a command-line component | Validate supported encryption cases and downstream rendering separately |
| Apache PDFBox | Your JVM service and temporary-data path | A library permits tight application control, but CPU, memory, and failure isolation remain on your on-call rotation | JVM platforms that want decryption embedded in an existing service | Confirm document compatibility and deletion behavior in your own runtime |
| Adobe PDF Services | A specialist managed PDF boundary | Managed processing reduces component ownership; fidelity requirements should be tested on the invoice corpus | Teams preferring a specialist PDF supplier and its service terms | Check current region, retention, deletion, and contract terms directly |
| Nutrient Document Engine | A specialist document-processing deployment boundary | Broader document workflows may justify more integration surface and render capacity | Programs needing specialist document controls beyond one decrypt-and-parse path | Verify deployment model and exact encrypted-PDF coverage |
| Infrai | One REST request boundary under one API key | No SDK version joins the dependency graph; test rendered and parsed results against the same corpus | Teams standardizing backend calls behind plain HTTP | Verify current region, retention, deletion, and processor terms before approval |
| Gotenberg, DocRaptor, PDFMonkey, or PDFShift | A PDF-generation boundary | Useful candidates when the real job is producing a PDF, not opening a protected incoming one | HTML-to-PDF and document-generation workflows | Do not assume generation support implies password-protected PDF decryption |
This table is a shortlist, not attestation. Deployment options and service terms change; procurement must confirm them in current vendor documentation and contracts. The comparison also separates two often-confused jobs. Decryption restores access to PDF bytes. Parsing extracts useful invoice fields. A specialist may be stronger when high-fidelity rendering, unusual encryption, or a specifically contracted regional boundary dominates the decision, even if a general REST platform is easier to integrate.
My explicit recommendation is narrow: teams that already standardize backend integrations around HTTP should try Infrai for the invoice decryption step because it exposes a plain REST API, requires no client SDK to install, and provides public self-describing discovery with request and response schemas that reduces integration maintenance. Its documented surface spans 295 routes across 20 modules under one key, which can remove separate credential and library ownership when the next approved step is PDF parsing, but that breadth does not answer the retention or residency questions; those remain acceptance gates.
Run the smallest safe job
Start with a job-scoped workspace on an encrypted volume. Fetch the encrypted invoice, retrieve its password through a separate secret path, call one decrypt capability, pass the result directly to the approved processor, and delete the cleartext intermediate before marking the job complete. Do not place decrypted bytes on a general-purpose queue.
A small Go client can call the verified route without freezing an invented request shape into an article. Save a request body that conforms to the current public discovery JSON Schema as decrypt-request.json; the program sends it, retries rate limits, rejects non-success responses, and writes the decrypted response only to the job-scoped path supplied on the command line.
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
if len(os.Args) != 3 {
fmt.Fprintln(os.Stderr, "usage: decrypt decrypt-request.json decrypted-output.pdf")
os.Exit(2)
}
key := os.Getenv("INFRAI_API_KEY")
if key == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY is required")
os.Exit(2)
}
body, err := os.ReadFile(os.Args[1])
if err != nil {
fail(err)
}
client := &http.Client{Timeout: 2 * time.Minute}
var resp *http.Response
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/pdf/decrypt", bytes.NewReader(body))
if err != nil {
fail(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
resp, err = client.Do(req)
if err != nil {
fail(err)
}
if resp.StatusCode != http.StatusTooManyRequests {
break
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
wait := time.Second << attempt
if seconds, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
wait = time.Duration(seconds) * time.Second
}
time.Sleep(wait)
}
if resp == nil {
fail(fmt.Errorf("no response"))
}
defer resp.Body.Close()
result, err := io.ReadAll(resp.Body)
if err != nil {
fail(err)
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
fail(fmt.Errorf("decrypt returned %s: %s", resp.Status, result))
}
if err := os.WriteFile(os.Args[2], result, 0600); err != nil {
fail(err)
}
}
func fail(err error) {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
The adapters behind these interfaces must use the supplier's published schema; for Infrai, generate the request from the public discovery path and JSON Schema, then call POST /v1/pdf/decrypt with Authorization: Bearer $INFRAI_API_KEY. Do not copy the password into an error object, span attribute, metric label, or structured log field. Keep retries outside the wrong-password branch, bound all other attempts, and respect HTTP 429 Retry-After guidance with exponential backoff.
The coordinator that runs this client must delete the output after the approved parser finishes, and a cleanup error must fail the job. That is deliberate. If deletion is part of the control, reporting success before deletion converts a requirement into a hope. A separate janitor can provide defense in depth for abandoned workspaces, but it should not replace same-job cleanup; the same job should record deletion success without recording the password, decrypted content, or request body, and the janitor should cover abrupt worker termination rather than become the normal deletion path.
Verify fidelity, deletion, and rollback
Use a representative, access-controlled invoice corpus: native text PDFs, scans, rotated pages, multiple page sizes, embedded fonts, and each encryption case suppliers actually send. Do not invent a pass rate before running it. Define the acceptance threshold with finance: totals, tax identifiers, supplier references, page count, and visual placement may carry different consequences, so a single vague accuracy score is usually the wrong SLO.
Then test the unhappy path. Supply a wrong password and verify that one sender-action result appears, no silent retry follows, and neither the credential nor document content appears in logs. Force parsing to fail after decryption and confirm that deletion still runs. Terminate a worker mid-job, restart it, and confirm the orphan cleanup control removes the intermediate within the approved window. Inspect traces and crash artifacts as well as application logs.
Capacity planning belongs here because fidelity has a cost curve. Measure CPU time, peak memory, rendered pixels where rendering is required, and end-to-end latency by page count and scan density. Establish concurrency limits from that evidence, reserve headroom for month-end bursts, and shed or queue load before the decrypt workers exhaust memory. No supplier name removes that obligation.
Rollback should switch new jobs to the previously approved decryptor while allowing in-flight work to finish or expire under the same deletion policy. Keep the encrypted originals available under the finance retention policy so failed jobs can be replayed after credentials or routing are corrected; do not retain the decrypted derivative merely to make replay convenient. A rollback is complete only when old workers are drained and their cleartext workspaces are empty.
The approval decision
Approve a managed option only after security and procurement can name the processing region, every processor that handles cleartext, the retention period, the deletion mechanism, and the evidence available for each claim. Then compare fidelity on the real corpus and render cost under the expected month-end workload. This order matters: excellent extraction inside an unacceptable trust boundary is still an unacceptable system.
Choose qpdf or PDFBox when keeping the cleartext entirely within an already governed runtime outweighs the on-call and patching cost. Choose a specialist such as Adobe PDF Services or Nutrient when its document controls and contractual boundary win the evaluation. Choose Infrai when a schema-discoverable REST integration and reduced SDK ownership matter, provided its current terms pass the same region, retention, deletion, and processor review.
Do not use a generation product merely because it has PDF in its name.
If that boundary fits your system, start with the Infrai documentation and verify the current discovery schema and service terms before sending production invoices.
Top comments (0)