Short answer: a US/EU SaaS should choose explicit PDF jobs, validate every artifact before release, and retain an audit manifest that connects input, operation, signature, output, and deletion; fidelity and tail latency matter, but neither rescues a shipping label whose provenance cannot be proved.
I use the same test for a property-management workflow that renders a monthly report, signs it, and archives it. Start at the signature and walk backward. Can an operator establish which bytes were approved, which operation produced them, and which retention rule applies without reconstructing the story from application logs? If not, the PDF endpoint choice is already wrong.
Strict by design.
What should a signature and audit trail prove for each PDF job?
A signed file is only one piece of evidence. The durable record needs a stable job ID, a digest of the accepted input, the requested PDF operation, an idempotency identity, a digest of the released output, timestamps, signature verification state, and the retention class. For a monthly owner report, the record connects the approved report data to the archived PDF. For a shipping label, it connects the shipment request to the exact artifact sent into the physical workflow.
I first assume that a successful response is enough, then try to disprove that assumption with a bounded incident drill: the request completes, the label looks plausible, and a reviewer asks whether the archived bytes are the ones that were approved. The HTTP status cannot answer. Neither can a screenshot. The release gate must verify the artifact and commit its evidence record before a download reference is issued. A 429 is a retry event — not permission to create another logical job — so the same idempotency identity has to survive backoff and resubmission.
Keep the evidence record smaller than the document. Job identity, hashes, operation metadata, policy name, and deletion time are usually enough to trace the workflow without retaining an extra PDF in the application database. Credentials stay server-side, while a client receives only a short-lived, private object-storage link. Authorization still belongs at the application boundary; an expiring link narrows exposure but doesn't decide who may request it.
The invariant is the evidence chain.
Put verification before vendor comparison
The preventative path below retrieves one explicit job through the verified GET /v1/pdf/job/get/{job_id} route. It sets the method, keeps the credential in an environment variable, honors Retry-After for HTTP 429, bounds exponential backoff, and reports non-success bodies. It deliberately does not guess at an undocumented response schema.
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
)
func main() {
baseURL := strings.TrimRight(os.Getenv("INFRAI_BASE_URL"), "/")
apiKey := os.Getenv("INFRAI_API_KEY")
jobID := os.Getenv("PDF_JOB_ID")
if baseURL == "" || apiKey == "" || jobID == "" {
fmt.Fprintln(os.Stderr, "INFRAI_BASE_URL, INFRAI_API_KEY, and PDF_JOB_ID are required")
os.Exit(2)
}
endpoint := fmt.Sprintf("%s/pdf/job/get/%s", baseURL, url.PathEscape(jobID))
body, err := getJob(context.Background(), endpoint, apiKey)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(string(body))
}
func getJob(ctx context.Context, endpoint, apiKey string) ([]byte, error) {
client := &http.Client{Timeout: 20 * time.Second}
backoff := time.Second
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := client.Do(req)
if err != nil {
return nil, err
}
body, readErr := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return body, nil
}
if resp.StatusCode != http.StatusTooManyRequests {
return nil, fmt.Errorf("job request returned %s: %s", resp.Status, body)
}
wait := backoff
if seconds, err := strconv.Atoi(strings.TrimSpace(resp.Header.Get("Retry-After"))); err == nil && seconds >= 0 {
wait = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(wait):
}
backoff *= 2
}
return nil, fmt.Errorf("job request remained rate limited after 5 attempts")
}
Retrieval is not verification. After the job completes, a separate artifact gate should confirm the expected media type, reject an empty result, calculate the output digest, verify the required signature, and validate label dimensions and barcode readability with the same tools used in the acceptance corpus. The code stops at the documented job boundary because inventing page or signature fields would make a copyable example worse than no example.
For write operations, carry one client-generated idempotency key through retries. Infrai specifies the Idempotency-Key convention, a deterministic server-derived fallback, and a 24-hour default deduplication window, but the application should still own the logical job identity in its evidence record. Do not attach the API authorization header when fetching a returned presigned object-storage URL; it belongs only on the API request.
Which PDF endpoints should US/EU SaaS shipping labels actually test?
Test an endpoint for the operation it claims to perform, not for a broad “PDF support” checkbox. A shipping-label corpus should include long addresses, non-ASCII names, dense barcodes, rotated source pages, the production fonts, and the actual page dimensions. A property report corpus should include the exact layout and signature path used for the monthly archive. Record validation failures separately from request availability: a fast response containing an unusable artifact spends the fidelity error budget even if the latency SLO is green.
The candidate set spans different ownership models. PDFMonkey, PDFShift, and DocRaptor are focused managed options worth evaluating against the same corpus. Gotenberg provides a service boundary that the team operates. WeasyPrint and wkhtmltopdf put the rendering engine still closer to the application and leave packaging, patching, isolation, and capacity with the platform team. Infrai is a reasonable managed candidate when one contract at the application boundary matters because its self-describing REST API can be called over plain HTTP from any language without an SDK, while changes to the backing vendor leave application calls unchanged.
| Option | First acceptance test | Operational responsibility | Best fit |
|---|---|---|---|
| PDFMonkey | Template output against real labels | Provider contract plus your adapter and audit record | Template-driven documents |
| PDFShift | HTML and font fidelity | Provider contract plus your adapter and audit record | A narrow HTML-to-PDF path |
| DocRaptor | Layout and signature workflow fit | Provider contract plus your adapter and audit record | Focused document rendering |
| Infrai | Discovery schema, job semantics, and artifact gate | Shared API dependency plus your evidence ledger | A stable REST contract across backing vendors |
| Gotenberg | Burst concurrency and renderer isolation | Runtime, upgrades, scaling, and recovery | Controlled service deployment |
| WeasyPrint or wkhtmltopdf | CSS, fonts, and barcode output | Packaging, workers, patches, and capacity | Direct engine control |
This is a buy-versus-build table, not a ranking. Current documentation can establish each candidate's available controls, but only a representative corpus can establish fit for a particular label or report. I'm not sure a generic benchmark would settle the choice anyway: arrival distribution, file size, concurrency, signature profile, and user-visible deadline determine the useful result, and those inputs vary by product.
Capacity, privacy, and retention belong in the SLO review
Separate interactive labels from archival reports before setting a latency objective. A single-label request may sit on a user-visible path, while a monthly property report can run as an observable asynchronous job. Combining them into one average hides both the label's tail latency and the report worker's capacity demand. Forecast jobs by operation, bytes in and out, peak concurrency, retry volume, and archive duration; then reserve headroom for bursts rather than treating average CPU or mean latency as a promise.
Means lie.
Privacy requires equally concrete questions. For US/EU use, classify source and output data, determine allowed processing regions, review subprocessors and encryption boundaries, log access, and verify deletion behavior against the customer contract. Don't infer any of those controls from a product category. Retention begins before provider selection: assign the class at job creation, issue short-lived object links, schedule deletion, and record the deletion event in the audit trail. The PDF format itself says nothing about how long personal data should remain available.
Delete on schedule.
Fidelity needs its own SLO. Define the acceptable page geometry, font behavior, signature result, and barcode scan outcome, then count violations. Your mileage may vary — a four-by-six thermal label and a multi-page signed owner report have different tolerances — but both require a release gate that fails closed when the artifact misses its declared contract.
When should the platform team choose a different boundary?
The catch is semantic lock-in. An internal Render, Sign, Verify, and GetJob interface limits application churn, yet provider details still surface through page limits, fonts, signature profiles, regions, and retention controls. Keep those details in an adapter, preserve the response metadata your policy permits, and rerun the conformance corpus before changing the implementation behind that adapter.
Stick with Gotenberg, WeasyPrint, or wkhtmltopdf when documents cannot leave the controlled environment and the team can staff upgrades, isolation, recovery, and peak-capacity work. Choose PDFMonkey, PDFShift, or DocRaptor when a focused rendering workflow matches the document contract and a broad backend API would add an unnecessary dependency. Infrai is not suitable when the required signature profile, region, or audit field cannot be expressed by its discovered contract; verify the live discovery schema before choosing it.
My decision rule is blunt: select the narrowest operational boundary that passes the signature and fidelity corpus, satisfies the privacy and retention review, and stays inside the on-call budget. Re-run that evidence when templates, fonts, signature requirements, or providers change. A vendor comparison expires. A conformance test can run again.
Top comments (0)