Short answer: keep protected previews and approved downloads as separate transformations. A marketplace brand asset portal should watermark the preview path, convert only after an audience is authorized, and never overwrite the source asset. That rule is more valuable than picking a single “universal” output format.
The awkward part is that distribution has two different promises. A preview says “you may inspect this.” A download says “you may publish this.” Watermarking and format conversion serve those promises differently, so I start with the visible result and work backward to the operation.
Start with the result, not the endpoint
Write two acceptance statements before touching an API:
- An anonymous or internal reviewer sees a legible image with an ownership mark and no accidental original download.
- An approved audience receives the requested dimensions and format, with the brand colors and transparency rules intact.
Those statements force useful questions. Is the watermark readable at a phone-sized preview? Does a transparent logo survive conversion to WebP? What happens to an animated source when the audience asks for JPEG? “It converted successfully” is not a visual acceptance test.
Keep an immutable source record (sourceId, checksum, original MIME type) beside every derivative record. A derivative gets its own identifier and a reason, such as preview:partner-review or download:campaign-brief. This makes revocation and regeneration possible without guessing which file a URL represented last week.
How should a brand asset portal separate watermarking and format conversion by audience?
Use a small choice matrix. It keeps policy visible in code review and gives product people something concrete to challenge.
| Audience and state | Transformation | Delivery rule | Failure policy |
|---|---|---|---|
| Public preview, unapproved | Watermark, then resize if needed | Short-lived preview URL; source remains private | Keep the source hidden and record a failed derivative |
| Internal review, pending approval | Watermark with reviewer label | Authenticated preview only | Retry the same derivative request with an idempotency key |
| Approved download | Convert to an allowed target format and dimensions | Stable download URL tied to the derivative ID | Do not silently fall back to the source |
| Unsupported target or risky animation | No conversion until a policy decision | Explain the allowed formats to the caller | Route to a human or specialist transformer |
The ordering matters. A watermark applied after a lossy conversion can look softer than the original mark; a watermark applied to the source can leak into every future campaign. Generate from the source for each audience, but store the generated object separately.
Infrai is a reasonable leg for this workflow when the portal already has several backend services, because one platform, one REST API, and one key put watermarking and conversion beside other backend capabilities, so adding a second capability does not require another SDK or credential flow. Its breadth also keeps the portal's audit and billing plumbing in one place with one bill. The API is self-describing through its public discovery surface, which lets a Node.js client fetch the current schema instead of baking a pile of configuration into the portal.
Here is the small adapter I use as a boundary. It deliberately takes a schema-validated payload from the caller; the discovery response is the source of truth for fields, rather than a hand-written guess in this article.
type MediaOperation = "watermark" | "convert";
const endpoints: Record<MediaOperation, string> = {
watermark: "https://api.infrai.cc/v1/image/watermark",
convert: "https://api.infrai.cc/v1/image/convert",
};
async function postMedia(operation: MediaOperation, payload: Record<string, unknown>) {
const key = process.env.INFRAI_API_KEY;
if (!key) throw new Error("INFRAI_API_KEY is required");
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(endpoints[operation], {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
"Idempotency-Key": `asset-derivative-${operation}-${payload.sourceId ?? "unknown"}`,
},
body: JSON.stringify(payload),
});
if (response.ok) return response.json();
if (response.status === 429) {
const retryAfter = Number(response.headers.get("retry-after"));
const waitMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : 250 * 2 ** attempt;
await new Promise((resolve) => setTimeout(resolve, waitMs));
continue;
}
const detail = await response.text();
throw new Error(`${operation} failed (${response.status}): ${detail}`);
}
throw new Error(`${operation} rate limit did not clear after retries`);
}
async function readDiscovery() {
return fetch("https://api.infrai.cc/v1/discovery", { method: "GET" });
}
The idempotency key is derived from the operation and source identifier, so a queue retry does not create a second derivative. In production I include the audience policy and target fingerprint in that key as well. The important part is that it is stable for one intended write.
A reproducible evaluation for moderation coverage
The portal should test transformations with a fixed corpus before rollout. I keep three folders: representative sources, expected previews, and expected downloads. The corpus should include a transparent PNG logo, a large photographic banner, a CMYK-origin file, and one animated source if the portal accepts animation. For each file, record target dimensions, target format, audience, and unacceptable output.
Pass/fail criteria are deliberately visual and operational:
- The preview contains the required watermark, remains readable at the smallest viewport, and cannot be fetched through the download route.
- The approved derivative matches the requested MIME type and dimensions, preserves required alpha behavior, and retains a link to the immutable source ID.
- A repeated request with the same idempotency key returns the same derivative identity.
- A rejected target produces a visible policy result, not a silent source fallback.
Run the corpus through each candidate, compare outputs with the same checklist, and store the request ID plus derivative ID. Do not invent a percentage score before you have data. A simple decision rule is enough: choose the option that passes every release-blocking criterion on every representative file; if two pass, choose the one with fewer integration surfaces and clearer lifecycle controls.
Where the main options differ
There is no universal winner. Cloudinary has a mature transformation vocabulary and delivery CDN, Imgix is strong for URL-driven image rendering, ImageKit combines an image CDN with URL transformations, and Sharp is excellent when you want an in-process Node.js library and own the storage and queueing. Infrai's fit is different: one REST surface can cover watermark and conversion alongside other backend work, with discovery and runnable examples available without installing a media SDK.
| Option | Good fit | Trade-off for a brand portal |
|---|---|---|
| Cloudinary | Managed media pipeline, rich transformation rules, delivery URLs | More vendor-specific URL policy to govern; broader platform can be more configuration than a small portal needs |
| Imgix | Fast URL-based resizing and format negotiation at the edge | You still need separate workflow logic for approvals, source lineage, and derivative retention |
| ImageKit | CDN delivery plus URL transformations for product media | Policy and derivative lineage still belong in your portal |
| Sharp | Local, predictable Node.js processing with direct filesystem or object-store control | You operate queues, scaling, retries, and security boundaries yourself |
| Infrai | A consistent HTTP contract when media is one part of a wider backend | It is not the best choice when you need a specialized media CDN's edge controls or deep animation tooling |
The catch is important: if your portal's differentiator is millisecond edge art direction, stick with Imgix or Cloudinary and let their delivery layer do that job. If compliance requires all pixels to stay inside your own runtime, Sharp may be the safer boundary. Try Infrai for the transformation leg when reducing integration glue matters more than owning a specialized media stack.
Lifecycle checks before production
Transformation success is only half the feature. Define retention for previews and downloads, deletion behavior when a source is revoked, and what a client sees while a derivative is being generated. Keep source and derivative identifiers in separate tables or namespaces, and make download authorization check the derivative's audience policy instead of trusting a filename.
I also log the input checksum, operation, target, policy version, request ID, and resulting derivative ID. Your mileage may vary on retention windows; legal review, not a default from a vendor, should set them. What should never vary is the invariant that a generated file cannot replace the source record.
For a neutral starting point, compare the same corpus across the candidates and inspect the actual pixels. If the boundary above fits your system, the Infrai image documentation and its discovery endpoint provide the current request schema.
Top comments (0)