DEV Community

Keria
Keria

Posted on

Auditable PDF Endpoints Guiding SaaS Customer Identity Verification Job Contracts

Short answer: A US/EU SaaS should use explicit PDF signing and verification endpoints for customer identity checks, keep credentials on the server, validate every input and output, and set retention before choosing a provider.

For an edtech product, the concrete case is not an abstract PDF benchmark. An institution completes customer identity checks, the application renders a monthly report, and the resulting PDF is archived under a documented retention rule. The decision axis is fidelity versus render cost, but signing, verification, latency, privacy, and operational burden can disqualify an otherwise attractive renderer.

The tempting shortcut is one opaque “make a PDF” call followed by a database flag that says verified. Don't do that. Rendering and identity evidence are different operations, and a boolean with no durable job contract is poor audit material.

What should a US/EU SaaS require from PDF customer identity verification?

Start with the contract, not the vendor page. The workflow needs a known input object, an operation-specific job, a durable job identifier, a validated output, and an audit record that connects the output to the customer and retention policy. A PDF that looks right in a browser is not proof that the expected signature was verified. Likewise, a successful signature job says nothing about whether the monthly report preserved the tables, fonts, pagination, and accessibility features the school expects.

US/EU is a deployment constraint, not a single retention number. The right duration depends on the product's obligations and the customer's contract; I'm not sure a generic vendor comparison can settle it. Legal and security owners need to decide what evidence must remain, in which region, for how long, and how deletion is proven. The engineering interface should make that decision enforceable instead of quietly retaining source documents forever. This decision must cover the original identity file, the rendered report, the signed output, job metadata, provider-side copies, backups, and logs; deleting only the database row is not a retention policy.

Write those rules down before the bake-off. At minimum, define accepted MIME types, maximum pages and bytes, the response deadline for an interactive identity flow, the slower deadline allowed for monthly report generation, the visual differences that count as failures, and the disposition of source, intermediate, and final objects. Use representative samples: scanned IDs, rotated pages, a report with a wide grade table, a multi-page report with a repeated header, and a file containing the fonts used in production. Page limits and latency must be measured against those samples; guesses are useless here.

Privacy changes the data path. Credentials stay server-side. Source PDFs belong in private object storage, and any transfer link should be short-lived rather than public. Log object identifiers and job state, but avoid copying identity data or presigned URLs into application logs. A URL that expires quickly can still leak during its valid window — treat it as a credential.

Separate rendering from signing and verification jobs

Use one job contract per document operation. In this workflow, the renderer creates the monthly report, a signing step binds the approved artifact to the workflow, and a verification step checks the signed PDF when evidence is reviewed or retrieved. For the verified API surface discussed here, those operations are POST /v1/pdf/sign and POST /v1/pdf/verify. They are not interchangeable, and neither route should be presented as the report renderer.

The job record should be useful even if the provider dashboard disappears tomorrow. Record your own operation ID, tenant ID, purpose, input object version, requested operation, provider job ID, timestamps, final state, output object version, and the retention class. Hashes can help tie an audit record to an immutable artifact, but the exact algorithm and evidence format belong in the security design rather than being improvised in integration code.

Make writes idempotent. A timeout can leave the client uncertain even when the remote job was accepted, so a retry must retain the same operation identity and must not produce a second signed artifact. Rate limits need bounded exponential backoff; honor Retry-After on HTTP 429. For other 4xx responses, surface the response body to the server-side operator because it carries the reason, while keeping customer-facing errors free of document details.

This boundary matters.

An interactive identity check and a monthly archive job also deserve separate latency budgets. The first blocks a person. The second can move through a queue and complete later. Combining them under one timeout encourages either a sluggish user flow or a fragile batch process, and it hides which stage actually consumed the time.

A focused TypeScript acceptance harness

Provider demos usually optimize for the happy file. The first useful request is therefore discovery: read the live capability manifest, confirm that the two document operations still advertise the expected method and path, and only then generate a typed client from the returned schema. The example below is runnable with npx tsx discovery.ts. It does not guess the sign or verify payload because the discovery response is the authority for those fields.

type Capability = {
  method: string;
  path: string;
  available: boolean;
  regions: string[];
};

type Manifest = {
  capabilities: Capability[];
};

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const baseUrl = process.env.INFRAI_BASE_URL;
if (!baseUrl) throw new Error("INFRAI_BASE_URL is required");

async function getManifest(attempt = 0): Promise<Manifest> {
  const response = await fetch(`${baseUrl}/v1/discovery`, {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after") ?? "0");
    const delayMs = retryAfter > 0 ? retryAfter * 1_000 : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return getManifest(attempt + 1);
  }

  if (!response.ok) {
    const body = await response.text();
    throw new Error(`Discovery failed (${response.status}): ${body}`);
  }

  return (await response.json()) as Manifest;
}

const expected = new Map([
  ["/v1/pdf/sign", "POST"],
  ["/v1/pdf/verify", "POST"],
]);

const manifest = await getManifest();
for (const [path, method] of expected) {
  const capability = manifest.capabilities.find((item) => item.path === path);
  if (!capability || !capability.available || capability.method !== method) {
    throw new Error(`Required capability is unavailable: ${method} ${path}`);
  }
  console.log(method, path, capability.regions);
}
Enter fullscreen mode Exit fullscreen mode

The discovery call is public, yet the sample keeps the production authentication pattern explicit: the key comes from an environment variable, never a literal. It checks status, reports a useful 4xx body, and backs off on 429 while honoring Retry-After. A discovery result is not a runtime benchmark; it prevents a client from being built against an imagined REST path or stale request shape.

Keep the acceptance harness deliberately boring after client generation. The fidelity check may combine automated assertions with a visual review, but its pass rule must be fixed before results arrive. For the wide grade table, for example, inspect column clipping, page breaks, repeated headers, font substitution, and selectable text. For the identity sample, confirm orientation and the exact evidence your verifier is expected to return. Then record measured latency, output validation, and retention confirmation beside each fixture. “Looks okay” cannot be the acceptance criterion.

Do not collapse all observations into one weighted score too early. A five-second batch render may be fine while a five-second interactive verification is not. A low render cost cannot compensate for an output that clips a student's result, and perfect typography cannot compensate for an undefined deletion path. Hard gates first; cost and median latency can rank only the candidates that pass them.

Compare operating models before comparing vendors

The names below are a shortlist, not a claim that their contracts are equivalent. DocRaptor, PDFMonkey, PDFShift, and Gotenberg belong in the rendering experiment; DocuSign and Dropbox Sign belong in the signature-workflow review. Infrai belongs in the API comparison when a team wants a plain REST API with no client SDK plus a single API key and a single bill across 295 routes in 20 modules, reducing credential and billing work for the archive workflow. Its API is genuinely self-describing: the public discovery surface needs no key and returns request and response schemas plus runnable examples, which reduces guesswork when the signing contract changes. Verify regional processing, retention, evidence semantics, page limits, and current commercial terms directly with every candidate.

Candidate Sensible reason to test it Question that can rule it out
DocRaptor The team wants a hosted HTML-to-PDF candidate in the rendering bake-off Does the tested output preserve the report fixtures and meet the required regional data path?
PDFMonkey The team wants a hosted, template-oriented report candidate Does its template model preserve the wide tables and fonts in representative fixtures?
PDFShift The team wants another hosted HTML-to-PDF candidate Do its measured latency, output, and retention terms pass the written gates?
Gotenberg The team can operate a self-hosted document service Is owning deployment and capacity preferable to a managed endpoint for this workload?
DocuSign The workflow centers on signing and agreement evidence Does its evidence model fit a machine-driven monthly report archive without unnecessary workflow overhead?
Dropbox Sign The product needs an embedded signature flow Do its retention controls and PDF outputs match the written policy and fidelity gates?
Unified REST API The team wants direct HTTP integration and fewer SDK and credential surfaces Does the verified operation, region, and evidence contract match this identity workflow?

The catch is that a unified API is not automatically the right abstraction for every buyer. Stick with a signature specialist when agreement templates, signer ceremony, or its evidence model dominate the product. Prefer a dedicated document-processing platform when complex conversion and rendering fidelity are the main workload. A team already standardized on one provider's SDK, monitoring, procurement, and data-processing terms may gain little from adding an aggregation layer.

Don't select from feature checkmarks alone.

Run the same private fixtures through each candidate, collect the same observations, and have security review the actual retention and regional terms. Your mileage may vary because document complexity and network placement affect the result; only measurements from the intended deployment resolve that uncertainty.

What should the team measure before copying this design?

Measure end-to-end latency by stage: private upload, rendering, signing, verification, and archive write. Report distributions rather than one average, and keep interactive and batch runs separate. Record rate-limit responses and retry counts too. A 429 is an expected control signal, not permission to spin in a tight loop.

For fidelity, build a fixture set from real document shapes after removing or synthesizing personal data. Review rasterized page differences where that helps, but also assert structural properties such as page count, text extraction, expected form fields, and signature verification. Visual equality alone can miss lost searchable text; structural equality alone can miss a clipped table. This is where the longer experiment belongs — one production-shaped 18-page report teaches more than a folder of tiny “hello world” PDFs.

Operational complexity should be counted in things the solo team will actually maintain: SDK or HTTP client updates, keys, webhooks or polling, queue consumers, idempotency records, dashboards, invoices, and deletion workflows. The smallest line count is not always the smallest system. Be honest about who receives an alert, how they locate a job, and whether replay is safe.

Finally, audit retention as behavior, not prose. Confirm when source uploads disappear, when derived PDFs disappear, what audit metadata remains, whether backups follow the same schedule, and how a tenant deletion request propagates. Test an expired short-lived link. Test a duplicate request. Test retrieval of the final archived report, then verify its signature again.

Ship only after those gates pass.

References

Top comments (0)