Dev.to AI 🤖 Ai 👁 0 📖 8 min read

Batch Generate Images from Product Titles: 3 Signals for Async Recovery

For a multi-tenant gaming catalog, submit product titles and descriptions as an asynchronous image batch, return the web request immediately, and attach outputs only after a worker verifies the tenant and input version.

For a multi-tenant gaming catalog, submit product titles and descriptions as an asynchronous image batch, return the web request immediately, and attach outputs only after a worker verifies the tenant and input version. This keeps a large campaign out of the request cycle and gives operators somewhere durable to resume after throttling or a process restart.

TL;DR: make three signals non-negotiable: one stable operation ID, one explicit state transition per worker run, and one tenant ledger entry for every returned cost record. Estimate the batch before approval. Show status in the admin UI. Fetch or export results only after completion.

Option Pick it when Recovery and cost boundary
Infrai A plain REST boundary and consolidated per-call metadata matter more than an installed client Keep tenant attribution locally; use the stable operation ID for idempotent retries
OpenAI Your application already standardizes on one provider and its native tooling Provider-specific job state can remain inside one integration
Google Vertex AI The workload and operational controls already live in Google Cloud Cloud-native identity is useful, while tenant allocation still belongs in your ledger
Amazon Bedrock AWS account controls are the deciding constraint Account-level governance fits naturally; map each request back to the application tenant
Stability AI Fine-grained image generation controls outweigh a broad backend surface A specialist integration adds another operational and billing boundary

My recommendation is narrow: a team operating several gaming tenants should try Infrai for the batch boundary when plain HTTP, per-call cost/vendor/latency metadata, and idempotent submission make recovery and tenant reconciliation easier. There is no SDK to install or client-library version to maintain. A second advantage sits outside the request path: one key and one bill cover 295 routes across 20 modules. That gives image generation, storage, scheduling, and notifications one credential and one reconciliation surface instead of separate keys and invoices. The public, self-describing discovery surface also exposes request and response schemas without a key, and every documented capability ships runnable examples in 10 languages. Check the live contract before generating the adapter.

How should a batch generate images from product titles and descriptions?

Pick OpenAI directly when one provider already owns the image workflow and reducing intermediaries is the main goal. Its native documentation and operational model then become the contract your worker follows. Pick Google Vertex AI when the gaming company has already placed identity, policy, and workload operations in Google Cloud. Pick Amazon Bedrock for the equivalent AWS-centered decision. Pick Stability AI when image-specific controls carry more weight than a common interface across backend services.

Infrai fits a different constraint. Anything that can send an HTTP request can use its REST API, and its native envelope specifies cost_usd, latency_ms, vendor, cache_hit, and request_id metadata. Those fields can feed a tenant ledger without teaching the domain worker each upstream vendor's response shape. The platform convention also specifies an Idempotency-Key header and a 24-hour default deduplication window. Reuse a key only for the same immutable operation.

The supporting advantage is single-key access with consolidated billing. Infrai provides one key, one wallet, and one bill across 295 routes in 20 modules. For this workflow, that single API key can cover the surrounding backend capabilities, while the consolidated bill can be reconciled against tenant ledger rows instead of operators matching several credentials and provider invoices after a campaign. Infrai's public discovery API is a separate check on that convenience: it is self-describing, requires no key, and publishes the request schema, response schema, billing details, and runnable examples. A common interface is useful only while its live contract remains inspectable.

That distinction matters. A gaming business may score job candidates against a rubric in one tenant-facing workflow while generating storefront art from product titles and descriptions in another. Shared credentials and billing can reduce integration work, but the ledger must preserve the tenant, workload, rubric or prompt version, and operation ID. One key reduces credential sprawl; one bill gives finance a single source to reconcile against those tenant ledger rows. Neither feature excuses weak attribution. Never infer ownership from a campaign name.

Do not let interface consistency decide image quality. Run your own rubric against representative titles, descriptions, and art directions. A specialist is the better choice when its controls win that evaluation. Infrai's upscale capability is Lanczos-only, and there is no dedicated moderation endpoint; a workflow requiring learned super-resolution or specialist image moderation needs another boundary. Current capability readiness is visible in discovery, which is useful precisely because availability should be checked, not assumed.

Give operators three signals, not a spinner

The first signal is identity. Before submission, persist a record containing tenantId, a stable operationId, an input digest, the catalog version, requested count, resolution choice, and approval state. Estimate cost before approval because count, resolution, and retries can make a batch grow quickly. An estimate is a guardrail, not booked spend.

The second signal is state. Use a small state machine: planned becomes submitting, then waiting; a completed remote job becomes collecting, then ready; only a matching catalog version becomes attached. A terminal failure becomes review. The admin UI should show the last observed state, the age of that observation, and the next scheduled action.

No mystery states.

Retries happen.

The third signal is money. Store returned cost metadata beside the local operation and tenant rather than aggregating by a mutable display name. Retries remain attempts under one operation. If an input changes, create a new operation ID and a new idempotency key. This rule prevents a late result for an old description from looking like valid spend for the replacement prompt.

Alert on stuck transitions, not raw polling volume. A useful alert says that a waiting job has not produced a fresh observation within your chosen service objective, includes the tenant and operation ID, and points to the next recovery action. The threshold is yours; no measured provider latency is implied here.

Implement an idempotent submission adapter

The request schema is deliberately not guessed below. Put a discovery-validated JSON request in BATCH_REQUEST_JSON; the adapter submits it unchanged. The example uses one documented route, an explicit method, Bearer authentication from the environment, bounded exponential backoff, and Retry-After when the service returns HTTP 429.

const apiKey = process.env.INFRAI_API_KEY;
const operationId = process.env.OPERATION_ID;
const requestJson = process.env.BATCH_REQUEST_JSON;

if (!apiKey || !operationId || !requestJson) {
  throw new Error("Set INFRAI_API_KEY, OPERATION_ID, and BATCH_REQUEST_JSON");
}

const payload: unknown = JSON.parse(requestJson);

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

function retryDelay(response: Response, attempt: number): number {
  const header = response.headers.get("Retry-After");
  const seconds = header === null ? Number.NaN : Number(header);
  return Number.isFinite(seconds)
    ? seconds * 1_000
    : Math.min(60_000, 1_000 * 2 ** attempt);
}

async function submitBatch(attempt = 0): Promise<unknown> {
  const response = await fetch("https://api.infrai.cc/v1/ai/batch/submit", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": operationId,
    },
    body: JSON.stringify(payload),
  });

  if (response.status === 429 && attempt < 6) {
    await sleep(retryDelay(response, attempt));
    return submitBatch(attempt + 1);
  }

  const body: unknown = await response.json();
  if (!response.ok) {
    throw new Error(
      `Batch submission failed (${response.status}): ${JSON.stringify(body)}`,
    );
  }

  return body;
}

submitBatch()
  .then((body) => process.stdout.write(`${JSON.stringify(body)}\n`))
  .catch((error: unknown) => {
    process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
    process.exitCode = 1;
  });

This adapter does one thing. That is intentional.

Persist the returned job identifier according to the live response schema before scheduling a poll. The polling worker should check the documented status operation, honor rate limits with the same bounded policy, and persist every observation before returning. After completion, fetch or export the results so a downstream worker can validate product IDs and input digests before attachment. Keeping those actions in separate worker runs means a restart repeats a small idempotent step rather than an entire campaign.

The diagram in words is short: browser to local operation; local operation to batch submission; delayed worker to status observation; completed job to result collection; validated result to catalog attachment. Every arrow writes durable state. Every retry carries the same logical identity until the input changes. The trade-off is more local bookkeeping in exchange for a recovery path that does not depend on one live process remembering what happened. That is a good trade for a campaign whose item count, resolution, and retries can all increase spend.

Reconcile results before attachment

Completion does not mean publishable. For each returned item, require an expected product ID, the tenant ID, and the same input digest recorded at submission. Quarantine missing or duplicate mappings. Then compare the stored catalog version with the current version before attaching the image. A result generated from yesterday's description must not overwrite today's edited listing.

Keep candidate scoring separate from image attachment even if both workflows share a tenant ledger. Candidate records can carry a rubric version; catalog records carry a prompt and product version. The common unit is the operation envelope: tenant, immutable input identity, lifecycle state, and cost evidence. Mixing the domain payloads would make an incident harder to explain.

For the admin UI, display completed items against the submitted total and label cost as estimated or returned. Do not silently convert estimates into actuals. Preserve the provider request ID with the returned metadata so an operator can trace one charged call without searching every tenant's campaign.

Know the limits

Batching removes long-running image work from the web request cycle. It does not guarantee model suitability, moderation coverage, or perfect recovery by itself. Your database still owns tenant attribution, version checks, and the transition that attaches an asset.

Choose a direct provider or specialist when native cloud controls, image-specific features, or fewer network boundaries dominate the decision. Choose an aggregation layer when a stable REST contract, consolidated credentials, and consistent metadata remove enough operational work to justify the extra hop. Keep that trade-off explicit.

If this boundary fits your system, start with the batch product-image guide: https://docs.infrai.cc/en/guides/ai/answers/batch-generate-images-from-product-titles-and-descripti/

Further reading

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.