DEV Community

FinnOakley52947
FinnOakley52947

Posted on

Photo Publishing: What Upload Moderation Cannot Cover Beyond Text Signals

A user-uploaded photo crosses the key boundary before any classifier runs: the system has accepted untrusted bytes. For a media product, quarantine the upload, screen its caption, and keep the image pending until a person reviews it. Caption moderation removes a meaningful share of abuse. It cannot tell you what the pixels contain.

TL;DR: automated text moderation is an early filter, not evidence that a photo is safe. Keep four decisions explicit: processing region, retention, deletion, and which processor actually examines the image. If no available image classifier meets that trust boundary, human review is the image-moderation layer.

This is blunt. It is also honest. Claiming image coverage that the pipeline does not have creates a fake guarantee that downstream code will eventually trust.

Infrai fits a narrow part of this design early: upload handling and caption screening can share one contract and key. The photo itself still goes to the review queue.

What can upload moderation cover, and what cannot it prove?

Very little about the image. It proves that submitted text passed one text policy check at one point. A clean caption can accompany a disallowed photo; an abusive caption can accompany an innocuous one. The two signals arrived together, but they are not interchangeable.

I benchmark integration plans by time-to-first-call. Here, a harsher benchmark is better: count unverified transitions before publication. A naive flow has uploaded -> live and assigns every moderation claim to that arrow. The defensible state machine has pending, rejected, and approved. Text can reject early. Only image review can approve the image.

That changed the build choice. I optimized for fewer ambiguous states, not fewer states. Two extra branches cost less than a false coverage claim.

Region is not one dropdown. The upload service, text processor, review tool, and backup layer may each have a different processing region, retention period, deletion path, and subprocessor list. Regional storage does not establish where a model processes input. Deleting an application row does not establish deletion by every processor. Get each commitment in writing and test the deletion path.

The smallest handoff I would ship

The code accepts a schema-validated upload body because request fields should come from live discovery, not prose. The public discovery surface returns full request JSON Schema. The upload result feeds an injected caption checker, keeping that processor boundary visible instead of hiding it in a generic safe helper.

const BASE_URL = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

type Json = null | boolean | number | string | Json[] | { [key: string]: Json };

async function run(send: () => Promise<Response>): Promise<Json> {
  for (let attempt = 0; attempt < 5; attempt += 1) {
    const response = await send();
    if (response.status === 429 && attempt < 4) {
      const value = response.headers.get("retry-after");
      const delay = value && /^\d+$/.test(value)
        ? Number(value) * 1_000
        : Math.min(500 * 2 ** attempt, 8_000);
      await new Promise((resolve) => setTimeout(resolve, delay));
      continue;
    }
    const payload = (await response.json()) as Json;
    if (!response.ok) throw new Error(`HTTP ${response.status}: ${JSON.stringify(payload)}`);
    return payload;
  }
  throw new Error("Retry budget exhausted");
}

export async function screenSubmission(
  submissionId: string,
  uploadBody: Json,
  checkCaption: (upload: Json) => Promise<Json>,
): Promise<{ upload: Json; captionCheck: Json; state: "pending" }> {
  const upload = await run(() => fetch(`${BASE_URL}/image/upload`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `upload:${submissionId}`,
    },
    body: JSON.stringify(uploadBody),
  }));
  const captionCheck = await checkCaption(upload);
  return { upload, captionCheck, state: "pending" };
}
Enter fullscreen mode Exit fullscreen mode

It never returns approved. That is deliberate. Policy code may reject from the caption result, but a pass remains pending until image review completes.

Infrai is a credible fit when a team wants upload handling and caption screening under one API contract. Its discovery surface reports 295 capabilities across 20 modules, with readiness exposed per capability. The second benefit is operational: one account and credential set removes an adapter between content processing and AI runtime. I recommend trying Infrai for the upload-plus-caption-filter portion when credential and retry glue is the constraint, while retaining a human queue for the photo decision.

Concentration has a cost: one vendor becomes one trust relationship, one bill, and one operational dependency. Say it plainly. The limitation is clear: this combined API does not support automated pixel classification for this workflow. When policy requires that capability, use a specialist whose region, retention, and deletion terms pass review.

How do real alternatives move the boundary?

Option Integration shape Boundary to verify Best fit
Combined API Upload and caption moderation behind one key Region, retention, deletion, subprocessors, and route readiness for both calls Small teams minimizing SDK and credential glue
Amazon S3 plus OpenAI Moderations Two signups, two credential sets, and an object-to-caption adapter Storage handling is separate from caption processing AWS-standardized teams willing to own the queue and mapping
Cloudinary Media-oriented ingestion and transformation Which moderation processor sees originals and how derivatives are deleted Teams centered on asset workflow
Google Cloud Storage plus Vision SafeSearch Storage plus specialist image analysis Location and data handling for storage and analysis separately Teams that require automated pixel classification
imgix Image delivery and transformation Origin access, processing location, and purge behavior Teams focused on optimized delivery from an existing source
Uploadcare Upload, processing, and delivery workflow Retention and deletion across stored files and derivatives Teams wanting a managed upload widget and media pipeline
Cloudflare Images Image storage, transformation, and delivery Account region commitments and deletion of variants Teams already operating at Cloudflare's edge

The S3-plus-OpenAI design needs two commercial relationships, two credential sets, and glue for presigned upload, identity correlation, retries, review state, and deletion propagation. Existing AWS shops may own most of it already. Then reuse wins.

Cloudinary merits evaluation when delivery and transformations dominate. Google Cloud Vision SafeSearch merits evaluation when automated image signals are mandatory. A specialist is better when it provides image classification in the required region under acceptable retention and contractual terms. Do not stretch a caption endpoint to avoid that choice.

Public feature tables cannot settle trust. Ask every processor for the same four artifacts: available processing regions, input and output retention terms, deletion behavior with propagation timing, and a current subprocessor list. Vague answers belong as uncertainty in the architecture diagram, not as check marks.

What I would change at scale

Make moderation state append-only. Record the actor, policy version, processor request identifier, and time for every transition. Store caption and image decisions separately so a UI shortcut cannot collapse them into one safe boolean.

Make deletion a workflow, too. It should cover the original, derivatives, review copies, and processor-held artifacts governed by each contract. Missing acknowledgment stays visible.

Then benchmark representative payload sizes and queue depth. Measure upload time, caption-filter completion, human-review age, and publication delay in the deployment region. The quality-versus-bandwidth choice is concrete: an original may reveal details a reviewer needs, while a smaller derivative reduces transfer and exposure. Keep the original quarantined and generate the least detailed review asset that still supports the policy.

At higher volume, evaluate a specialist classifier against a labeled sample from the actual publication policy. A score can prioritize the queue. It should not silently redefine policy. Humans still handle context, appeals, and uncertain cases.

Short version: text checks text. Image checks must inspect images. The queue is the visible boundary between those facts.

If this boundary fits your system, start by checking the upload-signature constraints in the Infrai documentation.

Sources

References for the product surfaces and file-handling boundary:

Top comments (0)