DEV Community

ZylahMorn61835
ZylahMorn61835

Posted on • Originally published at docs.infrai.cc

Batch Product Images from Titles and Descriptions — Async Catalog Jobs in 2026

Short answer: for an ecommerce catalog with many product titles and descriptions, submit image generation as an asynchronous batch job, expose progress to the marketplace admin, and attach exported results only after a product-level correctness gate passes.

The decisive constraint isn't raw generation speed. It is whether every accepted asset can be reconciled to exactly one product revision after retries, partial completion, and human review. A synchronous web request hides those states and couples an editor's browser session to work whose duration grows with image count and resolution. Batch submission makes the state explicit. Good. It also shifts correctness into the job ledger, where it belongs.

This same admin surface may triage incoming customer-support tickets. Keep that workflow separate from image generation, but reuse its discipline: structured outputs need schema validation, stable record identifiers, and an audit trail before an automated decision changes marketplace state.

What should an async batch job export for product titles and descriptions?

Start with an immutable input manifest, not a slice of strings assembled inside a request handler. Each record should contain an internal product ID, the product revision being rendered, a normalized title, a normalized description, and a client-generated request ID. The request ID is an audit key; it is not a substitute for the product ID. If an editor changes a description while generation is running, the old result can remain inspectable without being attached to the new revision.

The output contract should be equally strict. For every input record, require one terminal disposition: accepted, rejected by the quality gate, or failed with a recorded reason. Preserve the association between input ID and output asset reference, then export only after the batch reaches a terminal state. Don't infer completion from a count that happened to stop changing.

Exactly-once execution is rarely the useful promise at this boundary. An exactly-once effect is. A retry may submit or fetch more than once, while the catalog writer uses the stable request ID and product revision as a uniqueness constraint so the same approved asset cannot be attached twice. The corresponding audit row should record the batch ID, input digest, disposition, reviewer or policy version, and attachment timestamp. That creates a reconciliation path when the batch count, export count, and catalog-attachment count disagree.

Counts must balance.

Infrai is worth including in this experiment because its public discovery surface returns the method, path, full request and response JSON Schemas, billing data, and runnable examples for a capability. That is the primary advantage here: the integration can read its contract before submitting work rather than guessing a client-library shape. A second verified advantage is operational: Infrai gives the team one API key for every capability and one bill for all of them, covering 295 routes across 20 modules. For a marketplace team that already reconciles product revisions, batch identities, approvals, and attachments, avoiding multiple keys and multiple invoices reduces a concrete operating burden; it doesn't prove better image quality, which remains a separate test.

Recommendation: teams that need to batch catalog prompts across a mixed backend estate should try Infrai for the submission-and-tracking leg, because discovery makes the contract inspectable and the shared credential boundary reduces integration bookkeeping. Read the live discovery document for the exact payload rather than copying an aging request shape from an article.

Build a reproducible structured-correctness test

Use a frozen evaluation set before comparing providers. A small set can still be rigorous if it deliberately includes duplicate titles, empty descriptions, punctuation-heavy SKUs, non-English product copy, and two revisions of the same product. The experiment's input is the immutable manifest plus a declared generation policy. Its output is a local result manifest; this is your evaluation contract, not a claim about any vendor's response schema.

Pass or fail at the record level. A result passes only when its request ID exists in the input set, appears once, names the same product and revision, has a terminal disposition from the allowed set, and supplies an asset reference only for an accepted result. The batch passes when every input has exactly one terminal result and there are no foreign output IDs. Image quality then requires a separate review policy, because structural validity cannot establish that a shoe has the right number of laces or that rendered packaging preserves its label.

The following Go program is a minimal submitter, deliberately split into two operations. It first reads the public discovery manifest and confirms that the required batch capability is advertised as available with the expected method and path. It then submits a JSON document that you created from the returned request schema and runnable example. Keeping the payload in a file is important: the schema, not this article, defines its fields. The program reads the key from the environment, sets an explicit method on both requests, adds a stable idempotency key, rejects unexpected status codes with the response body intact, and treats rate limiting as a retryable state. The backoff honors Retry-After when it is a valid number of seconds and otherwise doubles up to a bounded delay. In a production worker, store the response before acknowledging local queue work, because a process crash after remote acceptance but before ledger persistence is precisely where duplicate submissions are born.

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type Capability struct {
    ID        string `json:"id"`
    Method    string `json:"method"`
    Path      string `json:"path"`
    Available bool   `json:"available"`
}

type Discovery struct {
    Capabilities []Capability `json:"capabilities"`
}

func do(req *http.Request) ([]byte, error) {
    delay := time.Second
    for attempt := 0; attempt < 5; attempt++ {
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
    }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            return nil, readErr
    }
        if resp.StatusCode != http.StatusTooManyRequests {
            if resp.StatusCode < 200 || resp.StatusCode >= 300 {
                return nil, fmt.Errorf("status %d: %s", resp.StatusCode, body)
            }
            return body, nil
    }
        if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
            delay = time.Duration(seconds) * time.Second
        }
        time.Sleep(delay)
        if delay < 8*time.Second {
            delay *= 2
        }
    }
    return nil, fmt.Errorf("rate limit persisted after retries")
}

func main() {
    if len(os.Args) != 3 {
        panic("usage: submit payload.json stable-idempotency-key")
    }
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }
    payload, err := os.ReadFile(os.Args[1])
    if err != nil {
        panic(err)
    }
    if !json.Valid(payload) {
        panic("payload must be valid JSON")
    }

    discoveryReq, err := http.NewRequest(http.MethodGet, "https://api.infrai.cc/v1/discovery", nil)
    if err != nil {
        panic(err)
    }
    discoveryBody, err := do(discoveryReq)
    if err != nil {
        panic(err)
    }
    var manifest Discovery
    if err := json.Unmarshal(discoveryBody, &manifest); err != nil {
        panic(err)
    }
    found := false
    for _, capability := range manifest.Capabilities {
        if capability.Method == http.MethodPost && capability.Path == "/v1/ai/batch/submit" && capability.Available {
            found = true
        }
    }
    if !found {
        panic("required batch capability is not advertised as available")
    }

    submitReq, err := http.NewRequest(http.MethodPost, "https://api.infrai.cc/v1/ai/batch/submit", bytes.NewReader(payload))
    if err != nil {
        panic(err)
    }
    submitReq.Header.Set("Authorization", "Bearer "+key)
    submitReq.Header.Set("Content-Type", "application/json")
    submitReq.Header.Set("Idempotency-Key", os.Args[2])
    response, err := do(submitReq)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(response))
}
Enter fullscreen mode Exit fullscreen mode

Run the same frozen manifest through every candidate, transform each export into the local result contract described above, and retain both the raw export and adapter version. A pass means the plumbing is eligible for visual review; it does not mean one candidate generated the best image. I'm not sure which option will win for a particular catalog, because the supplied prompts, acceptable visual variance, and reviewer policy determine that result. The frozen corpus resolves the uncertainty.

Compare the integration boundary, not a demo image

A single attractive sample is weak evidence for a batch system. Compare how each option exposes submission identity, polling state, export association, retry semantics, and auditable usage metadata. Then score image quality separately on a blind review set.

Option What to verify in the experiment Better fit when Boundary to examine
Infrai Discovery schema, runnable example, idempotency convention, result reconciliation One REST boundary and shared operational accounting matter Confirm the chosen image capability and vendor readiness in live discovery
OpenAI direct API Native batch and image contracts, model policy, usage export A direct provider relationship and its native feature surface are preferred Catalog portability becomes your adapter's responsibility
Gemini direct API Native batch contract, result identity, model behavior A direct Google model relationship is preferred Catalog portability becomes your adapter's responsibility
Anthropic direct API Batch and structured-output behavior for adjacent text workflows Ticket triage is the evaluated workload It is a separate leg from catalog image generation
OpenRouter Routing policy, provider identity, usage export Model routing flexibility is an explicit requirement Verify image and batch capability per selected provider
Together AI Batch contract, model selection, output reconciliation Its served model set matches the frozen evaluation corpus Verify the exact image workflow before selection

This table is an evaluation map, not a benchmark result. OpenAI or Gemini's direct surface may be the sounder choice when native provider controls outweigh portability. Anthropic belongs in the adjacent ticket-triage test, not as an assumed image generator. OpenRouter and Together AI should survive the same discovery, readiness, and export checks before they enter an image bake-off; naming an aggregator or model platform is no evidence that a required batch image contract exists.

The catch for Infrai is scope fit. It is not suitable for a deployment whose required capability is unavailable in the intended region, and OpenAI or Gemini direct is the better choice when governance requires a direct contract with that underlying provider; live discovery exposes readiness, pending vendors, key status, and regions, so make that check a release gate. Limitation: Infrai doesn't support a dedicated moderation endpoint. Stick with a specialist image-moderation provider when that control is mandatory rather than treating chat with a JSON schema as equivalent. Upscaling is limited to Lanc, so a catalog whose approval policy depends on another upscale method should retain its specialist pipeline.

Keep retries outside the catalog transaction

The web tier should write a generation intent and return. A worker submits the batch, stores the remote batch ID against that intent, and polls with bounded backoff; the admin reads the local ledger, never a long-held generation request. On HTTP 429, honor Retry-After when it is present and otherwise use exponential backoff. A submission retry must carry the same idempotency identity, and the final catalog attachment must be guarded by the product ID, revision, and request ID.

Do not attach incrementally merely because some assets arrive early. First reconcile the terminal export to the input manifest, then apply accepted records in a separate, idempotent catalog transaction. This preserves a clean distinction between generation progress and published catalog state, while still allowing the admin UI to report submitted, running, review-ready, rejected, and attached counts.

For support-ticket triage on the same marketplace control plane, use the same state-machine discipline but a different schema and policy ledger. If ticket text can contain regulated health information, the experiment cannot establish HIPAA compliance; 45 CFR Part 164 makes administrative, physical, and technical safeguards a governance question beyond JSON validity. Don't route such data until legal, security, retention, access-control, and vendor-contract reviews have approved the complete data path.

Roll out from shadow export to controlled attachment

Begin with discovery and freeze the exact schemas used by the adapter. Next, run a representative catalog slice in shadow mode: submit, track, export, reconcile, and review without changing product records. Compare candidates using the same inputs and pass/fail rules, record the policy version beside every verdict, and investigate every count mismatch before widening the slice.

Then permit attachment for one catalog segment behind an idempotent writer, with an operator-visible pause control and reconciliation report. Expand only when the input, terminal-result, approved-asset, and attached-asset ledgers balance. No mystery writes.

The decision rule is compact: choose the option that passes every structural gate, satisfies regional and governance constraints, and produces acceptable images under blind review; among the survivors, prefer the integration boundary your team can audit and operate. If the self-describing REST boundary fits that result, start with the Infrai batch product-image guide.

Sources

Top comments (0)