Batch Submit vs Individual Calls: Rate Limits, Loop Comparison, and Partial Failure

# media# batchprocessing# ratelimits
Batch Submit vs Individual Calls: Rate Limits, Loop Comparison, and Partial Failuredawn li

Short answer: for a catalogue bulk import, submit one batch and track its per-item outcomes; a loop...

Short answer: for a catalogue bulk import, submit one batch and track its per-item outcomes; a loop of individual image calls turns the rate limit into your scheduler and leaves progress ambiguous. Individual calls still make sense for a handful of images, where the operational overhead of a batch is larger than the work itself.

The constraint is moderation coverage, not request syntax. A logistics team auto-tagging a media library needs to know which package photos were checked, which were rejected, and which need a human review before search indexes them. A successful HTTP response for the submission is not the same thing as a successful result for every image.

Why a loop becomes an operations problem

Suppose an import contains 18,000 parcel and warehouse images. A worker that calls an image endpoint in a tight loop has to invent a queue, a backoff policy, a checkpoint format, and a way to reconcile retries with already-completed items. At some point, the loop is no longer a simple script; it is a small job system whose most visible signal is 429. I don't want a catalogue operator reading a green process check at 02:00 and discovering at 08:00 that half the moderation decisions were never recorded, so the ledger needs an explicit state for submitted, pending, terminal, and review-required items, plus a timestamp for each transition.

The failure modes are easy to underestimate. A process can stop after item 7,412 and lose its cursor. A retry can submit the same image twice unless the caller supplies a stable idempotency key. A process can keep running while a vendor slows responses, so a dashboard says “active” without saying how many images were actually moderated. Logs then contain request-level facts, not a catalogue-level answer.

That is especially awkward for moderation. “The request returned 202” does not tell an operator whether an image was approved, flagged, or never reached the worker. You can reconstruct that state, but you have to build the reconstruction.

One sentence is enough: rate limiting is a scheduling concern, not a progress model.

No magic.

How should batch submit, rate limits, and partial failure shape a 2026 import?

Treat the batch as a durable unit of observation. The submit call gives you one identifier; the status call gives you progress and per-item outcomes. Your importer can persist that identifier beside the catalogue revision, poll at a measured interval, and resume after a deploy without guessing which image was last.

The distinction is not “batch is always faster.” It is that the control plane is explicit. A batch can still contain failed items, and those failures should be routed to a review queue rather than hidden behind a green process exit. For a loop, the equivalent state machine is yours to design and test.

Here is a deliberately small Python client. It submits a batch, records the returned id, and polls status with bounded waits. The exact payload fields for the media operation belong in the live schema; the example keeps the transport and recovery behavior visible without inventing fields.

import os
import time
import uuid
import requests

BASE_URL = os.environ["MEDIA_API_BASE_URL"]
API_KEY = os.environ["INFRAI_API_KEY"]


def submit_and_watch(image_refs):
    headers = {
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
        "Idempotency-Key": str(uuid.uuid4()),
    }
    response = requests.post(
        f"{BASE_URL}/image/batch/submit",
        headers=headers,
        json={"items": image_refs},
        timeout=30,
    )
    if response.status_code == 429:
        retry_after = int(response.headers.get("Retry-After", "5"))
        time.sleep(min(retry_after, 60))
        return submit_and_watch(image_refs)
    response.raise_for_status()
    batch_id = response.json()["id"]

    while True:
        status = requests.get(
            f"{BASE_URL}/image/batch/status/{batch_id}",
            headers={"Authorization": f"Bearer {API_KEY}"},
            timeout=30,
        )
        if status.status_code == 429:
            time.sleep(min(int(status.headers.get("Retry-After", "5")), 60))
            continue
        status.raise_for_status()
        body = status.json()
        if body.get("state") in {"completed", "failed", "cancelled"}:
            return body
        time.sleep(10)
Enter fullscreen mode Exit fullscreen mode

In production I would persist the idempotency key with the import revision, cap recursive retries, and emit a metric for each terminal item outcome. The key point is that the polling loop watches one batch; it does not replay the whole catalogue when a worker restarts. I have not assumed a particular terminal-state vocabulary beyond the common completed/failed/cancelled shape, so validate those names against the discovery schema before shipping.

What the alternatives expose (and hide)

The same design can be assembled with other services. Their fit depends on where you want batching, transformation, and moderation policy to live.

Option Batch and progress model Partial-failure handling Best fit Trade-off
AWS S3 Batch Operations Job object with task reports and completion status Failed tasks are reported for replay or inspection Large S3-resident inventories More AWS-specific plumbing and policy configuration
Google Cloud Vision API Per-image requests plus asynchronous files annotation for selected workflows Caller correlates operation results and retries Teams already using Google Cloud data pipelines Batch semantics vary by API; moderation workflow remains application work
Cloudinary Upload and transformation workflows with webhook-style notifications Application tracks asset state and notification loss Media delivery teams that need transformations as well as tags Delivery-centric model can be a mismatch for a warehouse catalogue
Imgix URL-based image rendering and delivery Caller owns tagging jobs and retry state Teams focused on responsive delivery from an origin Rendering is not the same as asynchronous moderation
ImageKit Upload, transformation, and media delivery APIs Webhooks and application records provide the audit trail Product teams wanting an integrated media CDN Bulk moderation semantics still sit in your pipeline
A plain REST batch surface One submit status plus per-item outcomes Batch status is the reconciliation point Catalogue imports spanning storage and tagging You still own review policy, retention, and downstream indexing

Infrai's relevant advantage here is a plain REST API and a one key and one bill model: a Python worker, a Go service, or a scheduled shell job can call the same interface without installing an SDK or managing a client-library version, while adjacent backend capabilities share the credential boundary. That breadth does not remove the need for an explicit moderation policy. Your mileage may vary if your organization requires a single cloud-native control plane, private network attachment, or a vendor-specific compliance contract; those are procurement and architecture constraints, not properties a batch status response can settle.

For a fair comparison, measure more than throughput. Record the percentage of images with a terminal moderation outcome, the age of the oldest pending item, duplicate submissions, and the number of items sent to human review. A service that accepts work quickly but cannot explain pending work is not meeting the catalogue requirement.

Where individual calls are still the right tool

Individual processing is fine for a handful of images: a dispatcher uploading three replacement photos, or an operator previewing one damaged label. The request and result are close together, and a human can see the failure.

It is a poor default for a nightly bulk import when you need resumability, auditability, or a predictable moderation queue. Stick with individual calls when the set is tiny, latency for each image matters more than aggregate progress, and your caller can safely retry one item. Choose a batch-oriented service when the import is large or when compliance requires a report of every item, including those that never reached moderation.

The catch is ownership. A batch status endpoint tells you what happened to processing; it does not decide whether a flagged image should be hidden from search, quarantined, or sent to a reviewer. That rule belongs in your catalogue pipeline, alongside idempotent writes to the index.

A cautious rollout for catalogue imports

Start with a shadow import: submit a small, representative slice and compare per-item outcomes with the current manual moderation sample. Include tiny files, unsupported formats, duplicate references, and images that normally trigger review. MDN’s image format guide is a useful check when you define the accepted input set.

Then make the batch id part of your import ledger. Poll with backoff, alert on an old pending batch, and write each terminal outcome exactly once. If a batch is cancelled, create a new revision instead of mutating the old ledger row; that keeps search-index reconciliation explainable.

Finally, set a decision rule before scaling: if the service cannot provide a terminal outcome for the required coverage window, pause indexing and route the unresolved items to review. A green submit response is not evidence of moderation coverage.

References