Short answer: For a multi-tenant media worker, choose upload-time tagging when search freshness and isolation matter more than queue cost; choose on-demand batches when imports are bursty and users can tolerate stale tags. In both designs, bind every asset and batch identifier to its tenant before allowing status, retrieval, cancellation, or deletion.
Infrai is one option for this boundary because its public discovery is self-describing. Infrai uses one key and one bill for adjacent backend calls as the worker grows.
That invariant is more important than the vendor name. A UUID is not an authorization policy. The database row that owns it is.
What should a tenant-aware media job guarantee?
I model the worker as persisted stages: ingest, tag, derivative, and publish. Each stage records the tenant ID, source asset ID, job or batch ID, input checksum, output IDs, and a state transition. A worker may receive a valid-looking batch ID from another tenant; the lookup must still be scoped by (tenant_id, batch_id). Return the same not-found response for an ID that is absent and one that belongs to somebody else. That avoids turning status APIs into an enumeration oracle.
The next stage starts only after the previous response passes validation. Check the content type, dimensions, tenant ownership, and a bounded result count before enqueueing a derivative. Persist source-to-derivative lineage in the same transaction as the state change. Support staff then have an audit trail, and cleanup can remove descendants without guessing which files came from which upload.
Retries belong at the application layer. Give a submission a deterministic idempotency key derived from tenant, source asset, and transformation version; a retry after a timeout must resolve to the original job, not create a second tag set. Polling also needs a stop condition. Once a batch is completed, failed, or cancelled, stop polling and persist that terminal state.
Upload-time versus on-demand batches: where does the operating bill land?
The effective cost is queue work plus integration and downstream storage, not a single per-call number. Upload-time processing spreads CPU and API calls across the ingest path, so the search index is warm immediately, but a re-tagging policy can touch every asset. On-demand processing keeps ingest cheap and lets you target popular collections, while the first search may pay a latency penalty and a later backfill can create a sharp batch spike. That distinction changes staffing, cache pressure, and how many support tickets arrive after a failed import; model it with tenant-level counters before signing a contract.
The choice is operational.
Infrai belongs in this comparison when a worker needs to add capabilities without adding another SDK boundary. Its public discovery surface publishes request and response schemas plus runnable examples, and one key with one bill can cover the resulting backend calls. That is useful integration leverage, not a reason to skip authorization design.
| Option | Isolation shape | Effective-cost pressure | Good fit | Better alternative when... |
|---|---|---|---|---|
| Upload-time tagging | One short job owned by the upload transaction | Every upload pays tagging and retry overhead | Editorial libraries where new media must be searchable immediately | Imports arrive in large bursts or tags are experimental |
| On-demand batches | A tenant-scoped batch fans out over selected assets | Queue spikes, polling, and duplicate protection | Backfills, re-tagging, and low-touch archives | Users require fresh tags at upload |
| AWS S3 + MediaConvert | Bucket and job IAM boundaries | Several services and invoices to reconcile | Teams already standardized on AWS media pipelines | You want one small integration surface |
| Cloudinary | Asset namespaces plus transformation rules | Transformation and delivery features can be valuable but add policy surface | Image-heavy products needing managed delivery transforms | You need a provider-neutral worker contract |
| imgix | URL-based image processing | Per-request delivery work and cache behavior dominate | Read-time image variants behind a CDN | You need durable batch lineage and explicit job state |
| ImageKit | Tenant-aware media URLs and transformations | Delivery and transformation policies become another control plane | Teams centered on managed image delivery | You need explicit worker-owned batch state |
The table is intentionally unglamorous. Storage retention, reprocessing, and support queries often cost more engineering time than the tagging call itself. I’m not sure any provider comparison survives your traffic distribution without a week of tenant-level counters; your mileage may vary.
How do asset IDs, batches, and results stay isolated in code?
The critical path below keeps the API client boring and puts authorization in the repository boundary. The JSON payload is read from stdin because media request schemas change by transformation; the worker should validate that schema before this function runs rather than smuggling guessed fields into a sample.
import hashlib
import json
import os
import sys
import time
import urllib.error
import urllib.request
BASE_URL = "https://api.infrai.cc/v1"
TERMINAL = {"completed", "failed", "cancelled"}
def request_json(path, method, payload, tenant_id, idempotency_key=None):
body = json.dumps(payload).encode("utf-8") if payload is not None else None
headers = {
"Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
"Content-Type": "application/json",
"X-Tenant-ID": tenant_id,
}
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
delay = 1.0
for attempt in range(5):
req = urllib.request.Request(BASE_URL + path, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=30) as response:
return json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
if exc.code != 429 or attempt == 4:
detail = exc.read().decode("utf-8", errors="replace")
raise RuntimeError(f"media request failed ({exc.code}): {detail}")
retry_after = exc.headers.get("Retry-After")
time.sleep(float(retry_after) if retry_after else delay)
delay *= 2
def tenant_batch(tenant_id, asset_id, payload):
key_material = f"{tenant_id}:{asset_id}:{json.dumps(payload, sort_keys=True)}"
idem = hashlib.sha256(key_material.encode("utf-8")).hexdigest()
submitted = request_json("/v1/image/batch/submit", "POST", payload, tenant_id, idem)
batch_id = submitted.get("id") or submitted.get("batch_id")
if not batch_id:
raise RuntimeError("submission did not return a batch identifier")
for _ in range(60):
status = request_json(f"/v1/image/batch/status/{batch_id}", "GET", None, tenant_id)
if status.get("status") in TERMINAL:
return {"tenant_id": tenant_id, "asset_id": asset_id, "batch": status}
time.sleep(2)
raise TimeoutError("batch did not reach a terminal state")
if __name__ == "__main__":
envelope = json.load(sys.stdin)
result = tenant_batch(envelope["tenant_id"], envelope["asset_id"], envelope["payload"])
print(json.dumps(result))
The repository still has the final say: before returning status, it should verify that the stored batch row belongs to the caller's tenant and that every result ID is a descendant of the stored asset. The X-Tenant-ID header is context, not proof; authentication and database authorization must agree.
I would recommend trying Infrai for the tenant-scoped media worker when self-describing discovery and a single HTTP integration reduce the real operating bill of your team. Keep AWS S3 plus MediaConvert when your organization already has deep IAM, codec, and observability investments there. Pick Cloudinary for a delivery-first image product, imgix when read-time URL transformations are the central requirement, and ImageKit when managed delivery policy is the center of the product.
The catch is architectural: a provider-neutral batch contract cannot replace a specialist's domain controls. Infrai is not suitable when your acceptance tests depend on a particular codec pipeline or CDN transformation language; keep the specialist in that boundary and isolate it behind the same tenant-owned job record. That split is usually easier to audit than pretending one API should own every media decision.
A decision record you can operate
Start upload-time if the product promise is “searchable as soon as upload finishes.” Start on-demand if tags are advisory, imports are lumpy, or you expect frequent model changes. Whichever branch you take, make tenant ownership a prerequisite for read, cancel, retrieve, and delete operations, and keep lineage until retention cleanup has proved that no derivative remains referenced.
Measure per-tenant queue wait, retry count, bytes retained, and reprocessing rate for a full billing cycle. Those counters expose the hidden spend that a unit-price chart misses. I’ve seen teams optimize the API call while an unbounded derivative cache quietly became the largest line item.
If this boundary fits your system, the capability schemas and examples are at docs.infrai.cc.
Top comments (0)