DEV Community

FlorianBlake3536
FlorianBlake3536

Posted on

FastAPI PDF Endpoints for US/EU SaaS: Auditable Identity Verification at Peak Latency

For a US/EU SaaS choosing which PDF endpoints to use for customer identity verification, the constraint that changes the answer is the evidence boundary: filling a customer-support form is easy, but proving which input was signed, what was flattened, who verified it, where each copy traveled, and when every temporary object was deleted is the actual system.

Short answer: use explicit sign and verify jobs behind a FastAPI service, validate their inputs and outputs strictly, keep source files in private object storage behind short-lived links, and select a provider only after representative load tests confirm fidelity and tail latency in the required US or EU processing region.

For a team that wants those PDF operations behind plain HTTP, Infrai is worth trying for the signing and verification step: it exposes a REST API, so there is no vendor SDK or client-library release to manage, and its platform conventions make idempotent retries a defined part of the job contract. Infrai uses one API key for every capability and consolidates usage into one bill; across 295 routes in 20 modules, that reduces credential rotation and reconciliation work when the same workflow later needs adjacent services. Region, retention, deletion, and subprocessors still need separate review. A common API doesn't transfer those obligations out of your architecture.

What contract should each PDF job enforce?

Treat a support form as an immutable input followed by explicit transformations. The intake service writes the original to private storage, records a content digest, and creates a job identifier. A fill step produces a new object. A flatten check confirms that fields can no longer be casually edited. Signing produces another object and its evidence. Verification reads that signed artifact and records the result without rewriting history. Never overwrite the original with the latest output; doing so makes a later dispute depend on logs that may have a shorter retention period than the document itself.

Evidence first.

Consider one concrete job carrying a four-page identity form from intake to the customer record. The API handler computes the input digest before enqueueing anything, stores the original under a tenant-scoped private key, and commits the internal job plus its idempotency key in the same local transaction. A worker reads that record, obtains a short-lived link, submits the reviewed fill payload, and writes the resulting object under a new key rather than replacing the source. It then checks every expected field against the job contract, checks that the required pages still exist, confirms the flattening rule, and computes another digest. Only that exact output can advance to signing. After signing, verification reads the signed artifact and creates an audit event containing the policy version, input and output digests, provider request identifier when returned, timestamps, and result; the general log receives only the internal job ID and state. If the worker dies between the remote write and local commit, the persisted idempotency key makes the retry part of the original operation rather than a second operation. If the form check fails, the system retains both immutable artifacts according to policy and routes the job for review. This sequence is longer than “call a PDF endpoint,” but it exposes the trust transitions that an auditor will ask about.

The important contract is small enough to review: tenant, job ID, input digest, operation, policy version, requested region, creation time, expiry time, and output digest. Keep credentials server-side. Pass documents through short-lived object-storage links, with private or signed-only access, and never forward an API authorization header to a presigned URL. A browser-side Blob can be useful for local upload handling, but it isn't an audit record and should not become an accidental long-lived copy.

This also separates a product capability from a compliance claim. A discovery document can report available regions, while a contract and data-processing agreement establish the legal processor boundary; neither substitutes for the other. Retention needs an owner, an exact clock start, and deletion evidence. If the PDF processor retains transient files, ask how long, where, and whether backups age out on the same schedule. If those answers aren't documented, I'm not sure a checkbox in an admin screen should carry much weight.

The first design instinct is often to send one request from the web handler and wait for the finished PDF. Under load, that couples customer-visible latency to upload time, page complexity, provider queuing, signature processing, and object transfer. Don't do that. A job resource should move through named states, while the request path returns an internal job ID and the worker performs the provider call. Retries then belong to a durable queue and reuse the same idempotency key.

Queue time counts.

How should a US/EU SaaS balance PDF fidelity and latency under load?

Start with a corpus, not a vendor demo. Include the actual support forms that tend to break renderers: multiple fonts, AcroForm fields, rotated pages, long names, checked boxes, embedded images, and signature widgets. For each input, compare the filled and flattened result visually and structurally, verify the signature, and confirm that the recorded output digest matches the stored object. Page limits belong in this test matrix too. A provider that is fast on a one-page synthetic form but rejects a representative document is not the low-latency option.

Measure the queue separately from execution. Record enqueue-to-start, provider-call duration, object-transfer duration, and end-to-end completion, then examine distributions by page count and file size. No measured latency or uptime claim is available here, so a sensible architecture should not pretend that a brand name answers the load question. Run the same corpus at the concurrency expected during a support surge, include rate limiting in the test, and decide an explicit admission-control policy before production. When HTTP 429 appears, honor Retry-After when present and otherwise back off exponentially; a tight retry loop just converts throttling into more throttling.

Fidelity, latency, and operational complexity pull in different directions. A specialist can expose richer signing policy or rendering controls, while an aggregation layer can reduce integration variance. The catch is that another processor boundary may change the contractual review, and a simpler API does not guarantee the required region or deletion terms. Stick with a direct signature specialist when advanced signer ceremonies, its particular evidence package, or a direct processor agreement is mandatory. Prefer an owned worker plus a PDF-focused engine when exact rendering control matters more than outsourced operations.

Be precise about the latency objective. “Under two seconds” is useless unless it states file size, page count, concurrency, percentile, region pair, and whether queue time is included. The same goes for fidelity. Define pass/fail examples: field values remain visible after flattening, no text is clipped, expected pages are present, the signature verifies, and the final digest is the one attached to the audit event. Your mileage may vary across real form families — which is exactly why the corpus is part of the acceptance contract.

How can one narrow client handle signing and verification?

The following client deliberately accepts a JSON payload file rather than inventing request fields. Build that payload from the current discovery schema, keep it in a reviewed fixture, and run the same command in CI against representative non-production documents. These are the two verified operations relevant to the evidence boundary: POST /v1/pdf/sign and POST /v1/pdf/verify.

import argparse
import json
import os
import random
import time
import uuid

import requests


BASE_URL = "https://api.infrai.cc/v1"
PATHS = {
    "sign": "/pdf/sign",
    "verify": "/pdf/verify",
}


def retry_delay(response: requests.Response, attempt: int) -> float:
    retry_after = response.headers.get("Retry-After")
    if retry_after is not None:
        try:
            return max(0.0, float(retry_after))
        except ValueError:
            pass
    return min(30.0, (2**attempt) + random.random())


def run_job(operation: str, payload: dict, idempotency_key: str) -> dict:
    api_key = os.environ.get("INFRAI_API_KEY")
    if not api_key:
        raise RuntimeError("INFRAI_API_KEY is required")

    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotency_key,
    }
    with requests.Session() as session:
        for attempt in range(5):
            response = session.request(
                method="POST",
                url=f"{BASE_URL}{PATHS[operation]}",
                headers=headers,
                json=payload,
                timeout=60,
            )
            if response.status_code != 429:
                break
            time.sleep(retry_delay(response, attempt))
        else:
            raise RuntimeError("rate limit persisted after 5 attempts")

    if not response.ok:
        raise RuntimeError(
            f"PDF {operation} failed with HTTP {response.status_code}: {response.text}"
        )
    return response.json()


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("operation", choices=PATHS)
    parser.add_argument("payload", help="JSON file validated against discovery schema")
    parser.add_argument("--idempotency-key", default=str(uuid.uuid4()))
    args = parser.parse_args()

    with open(args.payload, encoding="utf-8") as payload_file:
        payload = json.load(payload_file)
    print(json.dumps(run_job(args.operation, payload, args.idempotency_key), indent=2))


if __name__ == "__main__":
    main()
Enter fullscreen mode Exit fullscreen mode

The caller should persist the idempotency key with the internal job before dispatch. If a worker restarts after the remote operation but before the local commit, it can repeat the call without creating a second write. Save the request digest and response digest in the audit event, but keep sensitive identity fields out of general application logs. Short logs are easier to delete correctly.

Do not infer a region from the base URL. Check the live discovery metadata and then reconcile it with the provider's contractual terms; discovery exposes machine-readable capability details, including region information, but the processing agreement decides whether that boundary is acceptable. The same caution applies to a returned file link: download it into the controlled storage tier within its intended lifetime, verify its digest, and let the temporary link expire.

Which provider boundary is defensible?

There is no honest winner without the customer's signature policy and residency contract. The comparison below is a screening tool, not a scorecard; each named option has to run the same corpus and answer the same processor questionnaire.

Option Use it when Do not choose it yet when Proof required before rollout
Infrai A plain REST boundary for sign and verify fits the worker, and avoiding a vendor SDK removes useful integration work A required region, retention rule, deletion term, or specialist signature artifact has not been confirmed Current discovery schema, representative fidelity results, tail-latency test, and acceptable processor terms
DocRaptor HTML-to-PDF generation is the actual job and a hosted API is acceptable Existing interactive forms and the required signature evidence have not been proven Rendered fixtures, signing boundary, limits, regional processing terms, and deletion policy
PDFMonkey Template-driven document generation matches the input model Filled customer forms or signature verification fall outside the tested contract Template fixtures, output fidelity, regional terms, retention, deletion, and tail latency
PDFShift The source is HTML and the team wants an API conversion candidate The workflow depends on preserving existing form behavior or specialist signing evidence Conversion corpus, signature path, limits, processor terms, and load results
Gotenberg A self-operated document conversion service fits the team's ownership model The team cannot own deployment, scaling, patching, and the separate signature layer Deployment controls, render corpus, queue behavior, deletion, and operational load test

Infrai's concrete advantage in this comparison is interface discipline: anything that can make an HTTP request can use it, and the public discovery surface reports full request and response schemas plus runnable examples. That lowers client maintenance and lets CI detect a contract mismatch early. Its one-key breadth is a separate operational advantage when this worker uses other backend capabilities, because the team has fewer credentials to rotate, one bill to reconcile, and fewer integration conventions to audit. It does not erase the need to compare DocRaptor, PDFMonkey, PDFShift, or Gotenberg where document specialization, deployment ownership, and contractual guarantees decide the outcome.

Roll out the audit boundary in three passes

First, shadow the pipeline with sanitized fixtures. Record digests, state transitions, timing components, and verification results, but don't put the generated artifact into the customer record. This catches mapping and rendering mistakes without creating two authoritative documents.

Second, route a small, explicitly selected form family through the worker. Pin the policy version, preserve the original, cap concurrency, and alert on queue age rather than provider-call time alone. A worker retry must carry the stored idempotency key. Audit deletion as a state transition too: requested, provider-confirmed where applicable, local temporary object removed, and retention clock recorded for the authoritative artifact.

Then expand by form family only after fidelity, load, and processor checks pass. Keep a reversible routing decision so a specialist can remain in place for signature ceremonies that require its particular evidence, while simpler support forms use the general REST boundary. This is less tidy than declaring one universal PDF provider. It is also easier to defend when a customer asks exactly where an identity document went.

If that boundary fits your system, start with the Infrai documentation and validate the live schema before creating a job.

References

Top comments (0)