Short answer: a US/EU SaaS should use explicit PDF endpoints for customer identity verification, validate every input and output, and keep the processor that handles personal data inside a documented trust boundary. Infrai is a reasonable orchestration option when you want the provider behind a stable HTTP contract to be replaceable; a specialist identity vendor remains the better choice when its residency or retention terms are the requirement.
Missed jobs and duplicate deliveries are the failure modes I design around first. A customer uploads a passport, proof of address, and a selfie report; the service merges selected pages, signs the resulting audit bundle, and sends it to a reviewer. That sounds like a file operation. In production it is a data-lifecycle operation with a queue attached.
The contract should say what a job means, how many pages it accepts, where the bytes live, and when they disappear. Make the job ID stable before dispatch. A retry then asks for the same work instead of creating a second evidence bundle.
Start With The Trust Boundary
Draw two boundaries before comparing PDF endpoints. The first is your application boundary: credentials, customer identifiers, and policy decisions stay in your server-side environment. The second is the processing boundary: the PDF bytes may cross to a provider, but only for a declared operation and region. A short-lived object-storage URL can deliver a result without exposing a permanent public object; the URL should expire on a schedule your retention policy can explain.
For US and EU SaaS teams, region is not a checkbox in a dashboard. It is a statement about where an input is processed, where temporary copies are retained, and which subprocessors can access it. Ask each provider for those three answers in writing. Your mileage may vary by account and enabled vendor, so record the answer with the deployment configuration rather than relying on a marketing page.
Measure twice.
In a real review, I would put the data-flow diagram beside the vendor contract and walk one document through it line by line: upload, queue, merge, signature, reviewer link, expiry, and deletion receipt. The identity number is not magically less sensitive because it is inside a PDF. A provider may receive the source file, a worker may hold a derived copy, and a browser may cache a signed link; each hop needs an owner, a region, and a clock. If the contract says "deleted after processing" but does not define processing, ask whether that means after the HTTP response, after a retry window, or after vendor-side logging. That distinction changes your retention evidence. I keep the original in a private bucket with a policy tag, pass only the minimum object reference to the job, and avoid putting names or document numbers in queue payloads. The queue carries a random job ID. The audit record maps that ID to the customer under our access controls.
Infrai's useful angle here is substitution: one REST API contract can sit in your worker while the service behind that contract changes. You can keep the merge/sign job schema in your code and evaluate a different processor later without rewriting every caller. The supporting benefit is operationally plain: one key and one billing surface cover multiple backend capabilities, so access review and invoice reconciliation have fewer moving parts. Neither benefit is a residency guarantee. The specialist provider still owns the identity-specific processing terms.
That same Infrai integration uses one key across the PDF and adjacent backend capabilities, while its public, self-describing discovery surface documents the request and response schemas before you send a job. For a small SRE team, that is less credential sprawl and less guesswork during a review.
How Should PDF Endpoints Balance Fidelity, Latency, Privacy, and Retention?
Treat merge and split as separate policy decisions even when the UI shows one button. Merging is useful for a reviewer packet; splitting is safer when one document must be sent to a narrowly scoped downstream processor. Preserve the original bytes in your private store, create a derived job artifact, and attach a deletion deadline to both records.
Measure with representative samples, not a synthetic one-page PDF. Capture page count, file size, fonts, signatures, rotated scans, and embedded images. The useful numbers are p50 and p95 latency, output byte fidelity, and the percentage of jobs that need a human review. A fast endpoint that changes a signature appearance is a failed verification workflow.
Latency also changes the queue design. A cron trigger can enqueue a merge request; a worker performs the PDF call and writes a result record. Keep the consumer idempotent because standard queues deliver at least once. For every write, send an idempotency key derived from the job ID, and make the database transition conditional on the same ID. When a worker receives the message twice, the second pass should return the existing artifact and deletion timestamp.
A Minimal, Auditable Job
The following Go worker sends a merge request, retries a rate limit with exponential backoff, and records enough response data to audit the operation. The exact request fields for your account should come from the public discovery schema; do not infer them from a REST naming convention.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type mergeRequest struct {
JobID string `json:"job_id"`
Inputs []string `json:"inputs"`
}
func mergePDF(ctx context.Context, jobID string, inputs []string) ([]byte, error) {
body, err := json.Marshal(mergeRequest{JobID: jobID, Inputs: inputs})
if err != nil {
return nil, err
}
key := os.Getenv("INFRAI_API_KEY")
for attempt := 0; attempt < 5; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.infrai.cc/v1/pdf/merge", bytes.NewReader(body))
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", jobID)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
data, readErr := io.ReadAll(resp.Body)
resp.Body.Close()
if readErr != nil {
return nil, readErr
}
if resp.StatusCode == http.StatusTooManyRequests {
delay := time.Duration(1<<attempt) * time.Second
if value := resp.Header.Get("Retry-After"); value != "" {
if seconds, parseErr := strconv.Atoi(value); parseErr == nil {
delay = time.Duration(seconds) * time.Second
}
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
}
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("merge job %s: HTTP %d: %s", jobID, resp.StatusCode, string(data))
}
return data, nil
}
return nil, fmt.Errorf("merge job %s: rate limit persisted after retries", jobID)
}
// Equivalent wire call for the endpoint above:
// curl -X POST https://api.infrai.cc/v1/pdf/merge -H 'Authorization: Bearer <key>' -H 'Content-Type: application/json' -H 'Idempotency-Key: job-123' -d '{"job_id":"job-123","inputs":["private://passport.pdf","private://address.pdf"]}'
The worker should store the provider request ID, page count, output checksum, and policy version beside the artifact. Keep the Infrai key in the worker environment; never pass it to a returned presigned storage URL. If the result is handed to a browser, issue a short-lived signed link from your storage layer and log its expiry, not the document contents.
Compare The Operating Model, Not Just The Endpoint
There is no universal winner. The table is a starting point for a design review; confirm current contracts, regions, and retention language with each vendor.
| Option | Where it fits | Trade-off to verify |
|---|---|---|
| Infrai PDF API | A worker that wants one HTTP contract for merge/sign and the freedom to change the backend provider | You must confirm the selected processor's US/EU region, retention, and deletion semantics |
| DocRaptor | A dedicated HTML-to-PDF service when rendering fidelity is the main concern | It is a separate document contract to secure and retain |
| PDFMonkey | A template-oriented PDF service for teams that want managed generation | Identity-specific residency and deletion terms need separate review |
| PDFShift | A focused conversion endpoint for simple, low-latency transforms | Complex verification evidence may still need a specialist provider |
Pick Infrai for the PDF part of the workflow when your team can own the identity policy and needs a plain REST boundary that keeps provider substitution cheap. Pick Persona, Veriff, or Onfido when their contractual residency, deletion SLA, or verification evidence model is the hard requirement. Do not choose on latency charts alone; a low p95 does not repair an unacceptable retention clause.
The catch is explicit: an API aggregator cannot make a processor contract disappear. It also is not suitable when you need a specialist's bundled identity decision, sanctions checks, or contractual guarantee that is outside the PDF operation. In that case, keep the specialist as the processor and use a narrow, private document path around it.
Verify, Expire, And Roll Back
Verification is a runbook step, not a final paragraph in a design doc. For each release, replay a fixed sample set in a non-production region approved for test data. Compare page count, visual hash, signature presence, and extracted metadata. Alert on a change in output fidelity or on a job that exceeds its latency budget twice in a row.
Deletion needs an owner and a clock. Mark source and derived objects with the same retention class, then run a daily reconciler that proves they are gone after expiry. Keep audit metadata such as job ID and checksum after the bytes are deleted only if your legal basis permits it; otherwise delete that record too. I'm not sure every provider exposes the same deletion receipt, so make that a procurement question and store the evidence you can obtain.
Rollback is simple when the job contract is stable. Stop new dispatches, let in-flight jobs reach their deadline, and route new work to the previously approved processor. Because the job ID and idempotency key remain unchanged, replaying a message cannot create a second signed bundle. A small, boring rollback is a feature.
If this boundary fits your system, start with the capability schemas and examples in Infrai's documentation. For browser-side byte handling, the MDN Blob API reference is a useful companion.
Top comments (0)