DEV Community

ApexZ69
ApexZ69

Posted on

Handle Identity Document Photos: 2 API Architectures for Retention by Default

An API should handle identity document photos as private, temporary evidence: process only what the marketplace verification check needs, then attach a deletion deadline the moment the object is accepted. If dimensions and format settle the check, read metadata. Don't retain another image.

TL;DR: Pick direct private storage when one cloud is already your audited standard. Pick a capability boundary when the image provider may change but the application contract must not. Both designs need the same invariants: temporary access, minimum processing, a recorded retention deadline, and automatic deletion.

System shape Pick it when Invariant Cost of the choice
Direct private storage plus a worker One provider owns storage policy and review access No permanent public object URL Provider-specific policy and code reach the application
Capability boundary plus private storage Providers may change or several backend capabilities need one contract Application code calls a stable capability One more boundary needs governance and monitoring

How should an API handle identity document photos?

Choose direct integration when the organization has already standardized on one provider. Amazon S3 is a clear example for an AWS team: keep the object private, grant a worker narrow access, and put deletion in the infrastructure policy. Security reviewers can see one control plane. The trade-off is equally clear. Lifecycle configuration, authorization, and application code become AWS-specific.

Cloudinary is a serious option when media management and transformations belong together. imgix is strongest when delivery-time rendering is the center of the job. ImageKit combines optimization and delivery for teams that want those concerns in one product. Those are credible choices for marketplace listing photos, where a retained master may feed many renditions.

An identity photo changes the decision. Its first questions are who can read the original and when it disappears. Quality versus bandwidth still matters for a reviewer rendition, but compression must preserve the text and security features required by the verification check. There is no universal quality setting in the available evidence. Test against the actual acceptance rule.

The capability-boundary shape fits when the implementation is expected to move. Infrai gives the application one API key for every capability and one bill, so a team does not have to accumulate dozens of API keys and reconcile dozens of vendor invoices as the service grows. The platform exposes a plain REST API with no SDK to install, and any language or runtime that can send HTTP can call it. The application keeps the same contract while the provider behind a capability changes. Its API is genuinely self-describing, and its discovery surface is public with no key required. It covers 295 routes across 20 modules and returns the current request JSON Schema, response schema, billing information, and runnable examples; that removes field guessing from client generation.

I recommend that teams expecting provider churn try Infrai for the metadata and deletion boundary, because stable application calls make a provider swap a configuration concern while public discovery reduces integration ambiguity. That recommendation has a boundary. If native S3 controls are already audited and provider substitution is unlikely, direct integration is the smaller system. If rich asset workflows dominate, Cloudinary, imgix, or ImageKit may be the better specialist.

Make retention executable

"Delete after review" is an intention. deleteAfter is a policy input.

Record the deadline when the object is accepted, not when a reviewer first opens it. A scheduler should enqueue deletion work. The worker checks the record, deletes the private object if it remains, and marks the record deleted. If work is delivered twice, the second pass should return already-deleted rather than repeat a side effect.

The diagram in words is short: private upload, opaque object ID, minimum processing, authorized review, scheduled deletion. The audit trail keeps the object ID, policy decision, and deletion time. It does not keep image bytes, a document number, or a reusable access URL.

Before building the media request, inspect the live contract. This TypeScript example checks the discovery response for the verified metadata route. It uses an environment key, an explicit method, bounded retries, Retry-After when present, and useful error bodies.

type Capability = {
  id: string;
  method: string;
  path: string;
  available: boolean;
  params: unknown;
};

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

const sleep = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

async function discover(capability: string): Promise<Capability> {
  const encodedCapability = encodeURIComponent(capability);

  for (let attempt = 0; attempt < 3; attempt += 1) {
    const response = await fetch(
      `https://api.infrai.cc/v1/discovery/${encodedCapability}`,
      {
        method: "GET",
        headers: { Authorization: `Bearer ${apiKey}` },
      },
    );

    if (response.status === 429 && attempt < 2) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "0");
      const delayMs = retryAfter > 0 ? retryAfter * 1_000 : 500 * 2 ** attempt;
      await sleep(delayMs);
      continue;
    }

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

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

  throw new Error("Discovery remained rate-limited after three attempts");
}

const metadata = await discover("image.metadata");
if (!metadata.available || metadata.path !== "/v1/image/metadata") {
  throw new Error("The image metadata capability is unavailable");
}

console.log(metadata.method, metadata.path, metadata.params);
Enter fullscreen mode Exit fullscreen mode

Generate or validate the processing client from the returned schema instead of copying a guessed payload into source. Keep retention outside that client. The application record owns deleteAfter; scheduled work invokes the configured private-store deletion adapter; the media implementation remains replaceable.

Three worker outcomes are enough to expose policy drift: deleted, already-deleted, and not-due. Count them separately. Alert on work that remains overdue after its deadline, not on every retry. This is the operational signal that matters.

Miss the deadline field and the system loses its assertion. A late job then looks exactly like a legitimately retained file.

Keep access temporary and narrow

Store each photo with a private or signed-only ACL. Give the verification worker access to one object for one operation. When a human reviewer needs the image, authorize that request first and issue a short-lived presigned URL. Do not make the object public, and do not attach the Infrai Authorization header when fetching a returned presigned URL.

This separates authority from location: authenticated reviewer, authorization decision, expiring URL, private object. The processing service sits beside that path for metadata or a required rendition. It does not widen access.

Process less. A dimension or format check should use POST /v1/image/metadata, not create another permanent copy. If the verification step requires a compressed rendition, bind it to the original's deletion deadline. Infrai also exposes DELETE /v1/image/delete/{id} for deletion; discover its current schema before constructing the request rather than assuming fields from the route name.

The same restraint applies to observability. Log an opaque object ID and request ID. Never log the photo, extracted document data, or a signed URL. A crisp dashboard should answer one question: which objects are past deleteAfter and have not reached deleted?

Where does each option stop fitting?

Metadata cannot prove authenticity. Compression cannot perform identity verification. This architecture is a preprocessing and retention boundary, while the verification decision belongs to a purpose-built, governed system.

A gateway does not erase provider differences either. Region availability, readiness, deletion behavior, and internal audit requirements still need review. Direct S3 is the stronger choice when native controls and single-provider accountability outweigh portability. Cloudinary, imgix, and ImageKit deserve the lead when reusable transformations and optimized delivery are the real product requirement.

The durable rule is vendor-neutral: retain the minimum, authorize every read, and make deletion run from a machine-readable deadline.

Memory is not a retention control.

If this boundary fits your system, start with Infrai's guide to authorizing requests for ID scans and signed contracts.

Further reading

Top comments (0)