all dispatches
Sep 16, 202615 min read

Bulk Product Image Generation: From Product Feed to Approved Assets

Bulk product image generation from a product feed: batches, approval gates and consistent output.

Bulk Product Image Generation: From Product Feed to Approved Assets

Generating one convincing product image is an image-generation problem. Generating 10,000 assets that still match the right SKU, follow the same visual system, survive failures independently, and land in the correct listing or advertising format is a production problem.

Bulk product image generation is the automated production of independently traceable image jobs from structured catalog data under shared visual rules. At production scale, the unit you manage is not just an image: it is a product job with a source SKU, template version, execution record, and acceptance state.

Bulk generation should not mean taking a CSV, turning every row into a prompt, and sending requests as quickly as possible. Each SKU or variant needs to become a traceable job. A versioned visual template controls the transformation. Validation decides whether the result is usable. Only then should the system produce the derivatives needed for listings, galleries, or catalog ads.

Product feed
    ↓
Normalized product job
    ↓
Versioned visual template
    ↓
Generation / editing
    ↓
Quality and policy checks
    ↓
Approved product asset
    ↓
Listing, gallery and advertising derivatives

The generation call is only one part of that path.

Start with a job schema, not a prompt

A product feed and a creative brief contain different kinds of information. Combining them in one long prompt makes both harder to control.

For catalog work, it helps to separate three layers.

Layer Example fields What it controls
Product truth SKU, variant ID, color, material, source image What must remain accurate
Creative template scene, background, lighting, framing, shot type What may change
Destination policy output format, dimensions, content restrictions What may be published

The first layer is not creative.

If the feed says a shoe is black leather, an output that turns it navy or changes the material has failed even if the image looks good. The same applies to packaging text, logos, handles, buttons, stitching, proportions, and other details that identify the actual product.

A normalized application-level job could look like this:

{
  "job_key": "SKU-1842:BLACK-42:catalog-primary",
  "sku": "SKU-1842",
  "variant_id": "BLACK-42",
  "product_name": "Leather Chelsea Boot",
  "category": "boots",
  "color": "black",
  "material": "leather",
  "source_image": "https://cdn.example.com/products/SKU-1842-BLACK-42.png",
  "template": "studio-primary",
  "destination": "catalog-primary"
}

This is not an each::labs API schema. It is an application record that keeps commerce data, generation configuration, and execution state connected.

Normalize the feed before you spend inference

A generation model should not be the component that discovers a missing source image or an invalid variant record.

Check the inexpensive things first:

  • required SKU and variant fields exist;
  • the source asset is available;
  • categories map to known template families;
  • controlled fields such as color or material use your canonical values;
  • the requested output type exists;
  • the record contains enough data for the selected template.

If a source file is not already available at an address the workflow can use, each::storage can accept an upload and return a public_url for downstream media inputs.

A bad row in a ten-product test is easy to spot. The same defect repeated across a supplier feed can create hundreds of unnecessary generations.

The feed supplies facts. The template supplies creative rules.

A template is a production contract with a version number.
A template is a production contract with a version number.

A template is a versioned production contract

“Use the same prompt” is a weak definition of consistency.

A production template should hold stable whatever belongs to that asset type: background treatment, framing, camera behavior, output branches, validation steps, and other transformation rules.

A primary listing template might allow:

- preserve product geometry
- preserve color and visible branding
- centered product
- neutral background
- fixed framing range
- one approved output

A lifestyle advertising template can permit more:

- preserve product identity
- change environment
- use campaign-specific composition
- generate several crops
- allow additional contextual elements

They may start from the same product, but they are different contracts.

Version before you fan out

Every derivative should be traceable to the visual system that produced it.

Your own records should be able to connect the output to identifiers such as:

sku
variant_id
workflow_id
workflow_version
template_id
template_version

This becomes useful as soon as a template changes.

Say lifestyle-v3 crops tall products too aggressively. If you know which outputs depend on that version, you can regenerate the affected set. If you stored only final URLs, the refresh becomes guesswork.

Versioning also protects a large catalog run from changing underneath itself. You do not want the first half generated under one workflow and the second half under another unless that split was intentional.

In each::workflows, the workflow version is part of the trigger path. The repeated catalog job can therefore point to an explicit version rather than relying on an informal prompt name.

Keep free text at the edges

Some products will always need descriptive instructions. That does not mean free text should carry facts your system already knows.

If color=black exists in the feed, do not make a generated sentence buried inside a long prompt the only place where that constraint lives. Pass structured product facts explicitly wherever the workflow or model supports them. Reserve prose for genuinely creative instructions.

The payoff is diagnostic clarity. When an output is wrong, you can ask whether product data, template logic, model behavior, or validation failed. You are not left debugging one opaque prompt.

Model the variant matrix before generating anything

SKU count is only the first multiplier.

A feed with 2,000 products can quickly become a much larger media workload:

SKU
 ├── Primary listing image
 ├── Secondary/gallery image
 ├── Lifestyle image
 └── Advertising
      ├── Square
      ├── Portrait
      └── Campaign variant

Automation makes it easy to create variants that nobody actually needs.

Separate them first:

  • catalog-required: needed for the product to be listed;
  • merchandising-required: gallery, contextual, or category-page assets;
  • campaign-requested: required by an active campaign;
  • experimental: being tested rather than generated by default.

A 2,000-SKU catalog with three listing outputs and four advertising variants can produce 14,000 requested derivatives. If half the ad variants have no active use, generating them early creates review work, storage, and inference cost without improving the catalog.

Generate outputs because they have a job, not because the branch exists.

A catalogue is ten thousand small jobs, not one enormous request.
A catalogue is ten thousand small jobs, not one enormous request.

What “bulk” actually means at 10,000 assets

There is no reason for an entire catalog to share one failure boundary.

A 10,000-item catalog is an application-level workload made up of many smaller executions.

The current each::workflows Bulk Trigger endpoint runs the same workflow with up to 10 input objects per request. It returns a shared bulk_id, while successfully queued items receive their own execution_id. An invalid item can fail without preventing valid siblings from being queued.

So there are three different levels to think about:

API batch size is not catalog size.
Level What it represents Identifier
Catalog run Your complete commercial workload, potentially thousands of assets Your own catalog_run_id
Bulk submission Up to 10 workflow input objects submitted together bulk_id
Workflow execution One individual workflow run for one input object execution_id

With 10,000 workflow jobs and ten inputs in each Bulk Trigger request, the catalog can require up to 1,000 bulk submissions.

10,000 workflow jobs
÷ 10 jobs per bulk request
= up to 1,000 bulk submissions

Your application still owns the outer queue: what needs work, what has already been submitted, what should wait, and how much should be in flight at once.

Bulk Trigger is a workflow-level batching primitive; it does not mean every direct model call on each::api has become a workflow batch.

Repeat a workflow only when the work is actually a workflow

Not every catalog operation needs orchestration.

If the job is:

source image → one model → output

repeated direct model calls may be cleaner. When one generation or editing operation is enough, you can call an image model directly; the Bria model family is one current example available through each::labs.

A workflow earns its place once the repeated job has dependencies:

source image
    ↓
controlled edit
    ↓
validation
    ↓
choice
 ┌──────┴──────┐
approve       retry/review
    ↓
output

The same applies when one accepted source fans out into several derivatives:

approved product
    ↓
parallel branches
 ├── square listing
 ├── portrait listing
 └── ad creative

If orchestration only adds another network hop, skip it. The workflow should remove complexity the application already has, not create architecture for hypothetical future complexity.

EachVisual is the higher-level product surface for commerce visual generation. Lower-level workflows make more sense when the application needs direct control over generation, validation, branching, and delivery.

Keep a catalog manifest above the API batches

Bulk Trigger groups a small set of executions. A full catalog run needs its own parent record.

For example:

{
  "catalog_run_id": "fall-2026-refresh",
  "template_version": "studio-v4",
  "expected_jobs": 10000,
  "submitted_jobs": 10000,
  "approved_jobs": 9734,
  "review_jobs": 188,
  "failed_jobs": 78
}

Those names and values are illustrative, not an each::labs object or benchmark.

A bulk_id answers:

Which executions were submitted together?

Your catalog manifest answers:

Which executions belong to this commercial production run?

You need both concepts.

A concrete Bulk Trigger integration

The current endpoint is:

POST https://api.eachlabs.ai/v1/workflows/bulk-trigger/{workflowID}/{versionID}

each::labs currently authenticates the request with the API key as a Bearer token in the Authorization header.

This example reads account-specific values from environment variables. The fields returned by workflow_input() are illustrative and must exist in the input_schema of the workflow being triggered.

import os
import requests

API_KEY = os.environ["EACH_API_KEY"]
WORKFLOW_ID = os.environ["EACH_WORKFLOW_ID"]
VERSION_ID = os.environ["EACH_WORKFLOW_VERSION_ID"]
WEBHOOK_URL = os.environ["CATALOG_WEBHOOK_URL"]

def chunks(items, size=10):
    for i in range(0, len(items), size):
        yield items[i:i + size]

def workflow_input(job):
    return {
        "job_key": job["job_key"],
        "source_image": job["source_image"],
        "product_name": job["product_name"],
        "color": job["color"],
        "material": job["material"],
        "asset_type": job["destination"],
    }

def submit_catalog_jobs(jobs):
    submissions = []

    for batch in chunks(jobs, 10):
        inputs = [workflow_input(job) for job in batch]

        response = requests.post(
            (
                "https://api.eachlabs.ai/v1/workflows/"
                f"bulk-trigger/{WORKFLOW_ID}/{VERSION_ID}"
            ),
            headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
            },
            json={
                "inputs": inputs,
                "webhook_url": WEBHOOK_URL,
            },
            timeout=30,
        )
        response.raise_for_status()

        result = response.json()

        submissions.append({
            "bulk_id": result["bulk_id"],
            "executions": result["executions"],
        })

    return submissions

One detail matters here. The current Bulk Trigger documentation does not define response-array ordering as a client-correlation contract.

Do not assume this is safe:

for job, execution in zip(batch, result["executions"]):
    ...

Instead, make a client identifier such as job_key part of the workflow's declared input schema. Completed workflow execution records include the original inputs, so the asynchronous result can carry that identifier back to your catalog record.

Conceptually:

job_key: SKU-1842:BLACK-42:catalog-primary
execution_id: 69ae8c7b-...
bulk_id: 550e8400-...
status: completed

The API executes work. Your commerce database remains the source of truth.

One edge case is currently underdocumented: an item that fails synchronously in the initial Bulk Trigger response can contain an error but no execution_id, and the documented response does not echo that item's original input or a client key. Prevalidate against the workflow contract before submission, retain the raw response for diagnostics, and do not assume stronger per-item correlation for that case than the API currently documents.

Reconcile asynchronously

A request that starts image generation should not have to remain open until the asset finishes.

each::workflows webhooks can notify your application when an execution completes or fails. In a bulk operation, each execution sends its own webhook and includes the shared bulk_id. Workflow execution results also expose the original inputs.

If job_key is one of those inputs, reconciliation becomes straightforward:

webhook arrives
    ↓
read inputs.job_key
    ↓
read execution_id + bulk_id
    ↓
find SKU + requested derivative
    ↓
record completed/failed state
    ↓
run validation or failure handling

The webhook documentation recommends treating execution_id as the idempotency key and bulk_id as the correlation key for the bulk operation. Polling the execution endpoints is still useful for recovery and administrative reconciliation.

Quality is a set of states, not a single score.
Quality is a set of states, not a single score.

QA needs states, not one quality score

A returned image is not automatically a usable catalog asset.

Canberk's POV

A model working is not the same as the product working. A successful request and a valid image establish that generation completed; they do not establish that the correct SKU was represented well enough to publish.

Collapsing all of that into one “quality” score hides important differences.

Validation layer Examples Typical action
Deterministic file exists, format, dimensions, aspect ratio automatic pass/fail
Product fidelity color, shape, logo, text, protected detail automated signal + review where needed
Destination output meets channel rules automatic rules where possible
Subjective composition, campaign attractiveness human/product decision

Start with deterministic checks

These are cheap and objective.

Before asking another model whether an image looks correct, the system can establish:

  • did the workflow produce the expected number of outputs?
  • are the files accessible?
  • are they in the required format?
  • do dimensions and aspect ratios match the requested asset type?
  • does every requested derivative have an output record?

A missing 1:1 image does not need an aesthetic model to diagnose the problem.

Product fidelity is different

The harder failures look plausible.

A generated boot can still look like a boot while the model:

  • turns black leather dark blue;
  • changes the sole;
  • removes a pull tab;
  • alters visible stitching;
  • distorts a brand mark.

The generation succeeded. The commerce job did not.

Listing workflows therefore need preservation rules that are stricter than a general standard of visual attractiveness.

Packaging creates the same problem. A polished render with invented label text is not a better product image.

Some fidelity checks can be automated or used to narrow the review set. Others depend on the product category and what the business considers protected. The system should represent that uncertainty instead of hiding it behind a universal pass/fail score.

For a deeper treatment of correcting and validating existing supplier photography, the marketplace product-image enhancement guide covers that narrower problem.

Human taste is not a deterministic validator

Advertising creative introduces a different question.

A system can confirm that an image has the requested dimensions. It may be able to confirm that the product is present and that the expected source variant was used.

None of that proves the composition is commercially strong.

Automated signals can reduce or prioritize review. They should not be presented as objective taste.

Make review status first-class data

Do not stop at success and failure.

An application-level lifecycle might look like this:

queued
    ↓
running
    ↓
generated
    ↓
validation
 ┌──────┼────────────┬───────────┐
approved regenerate human_review rejected

Those are catalog states, not the official each::workflows execution statuses. The workflow itself currently reports running, completed, failed, or cancelled; your application can add the product-acceptance layer on top.

That distinction lets the catalog tell apart:

  • failed inference;
  • invalid media;
  • technically valid media that represents the wrong SKU;
  • an asset worth regenerating;
  • an asset that needs judgment;
  • an asset cleared for publishing.
Retry the tile. Never the whole mosaic.
Retry the tile. Never the whole mosaic.

Retry the failed asset, not the catalog

Bulk execution helps because failures can remain local.

The current Bulk Trigger contract allows one item to fail without receiving an execution_id while valid siblings receive queued executions.

Keep that same boundary afterward. If product 803 fails, products 801 and 802 should not pay for it again.

Different failures need different recovery

A practical taxonomy might look like this.

Transient execution failure
The input is valid, but the remote operation failed temporarily. Another attempt may make sense.

Input failure
The source is missing, corrupt, inaccessible, or violates an input requirement. Repeating the same request unchanged is unlikely to help.

Product-fidelity failure
The file is valid, but the generation changed something that should have remained true. Regeneration with different configuration or routing may be appropriate.

Destination failure
The master asset is usable, but one derivative does not satisfy its target policy. Fix that derivative rather than rebuilding the master.

Human rejection
The output is technically valid but commercially weak. That is product feedback, not an infrastructure outage.

Without the failure class, “retry” is just a habit.

Preserve successful intermediates

Suppose a clean, isolated product master has already passed, but a lifestyle branch fails. There is no reason to rerun product isolation unless the failure points back to that intermediate.

The same applies when one ad crop fails after the listing image is already approved.

A scalable system remembers what has already become trustworthy.

If product isolation is the actual problem you are solving, the AI background remover API for e-commerce guide covers that narrower operation without turning it into the architecture for the whole catalog.

One trusted product, two publishing policies

Listing assets and advertising creative can share a source without sharing the same permissions.

Approved product master
        │
        ├── Listing branch
        │     ├── primary
        │     ├── gallery
        │     └── destination derivative
        │
        └── Advertising branch
              ├── lifestyle scene
              ├── campaign layout
              └── channel crops

Listing branch: preserve more

A primary listing image is a poor place for creative ambiguity.

Its workflow should put tighter bounds around:

  • product color;
  • shape and proportions;
  • branding;
  • product count;
  • visible features;
  • crop;
  • background treatment.

The exact publishing constraints belong in a destination-policy layer. Meta catalog assets, Google Merchant imagery, TikTok catalog creative, and other destinations do not share one permanent universal specification.

That is why marketplace compliance should not be buried inside the generative prompt.

Channel rules change. Product identity should not.

Marketplace validation should remain its own configuration layer because those rules move. Google Merchant currently requires generative-AI product images to retain metadata indicating their synthetic source, and it has announced a 500 × 500 minimum image size across product categories beginning January 31, 2027. Those are publishing constraints, not creative-template instructions.

Keep destination checks updateable without redesigning the upstream visual template.

Advertising branch: allow controlled creativity

After you have a trusted product representation, advertising assets can permit changes that would be inappropriate in a primary listing:

  • contextual environments;
  • stronger crops;
  • campaign compositions;
  • promotional layouts;
  • different aspect ratios;
  • several creative directions.

The product still has to remain the product.

This is the useful connection between catalog generation and catalog ads. You do not need two unrelated visual systems. You need one trusted product layer with different downstream permissions.

EachVisual focuses on commerce product visuals. EachFashion covers the more specialized fashion branch, where garment and model identity introduce additional constraints.

When each::workflows is useful—and when a direct call is enough

Do not build a workflow because the diagram looks more sophisticated than an API call.

A direct model call can be the better architecture when:

  • the job has one operation;
  • there is no reusable intermediate;
  • the application already owns validation;
  • there is no branching;
  • there is no meaningful dependency between steps.

A workflow becomes more useful once the repeated production unit includes:

  • dependent operations;
  • parallel derivatives;
  • validation;
  • conditional routing;
  • reusable intermediate outputs;
  • a stable input/output contract.

For example:

source product
    ↓
edit
    ↓
validate
    ↓
choice
 ├── approved → output
 └── failed   → review path

That is a workflow because the steps depend on one another.

This:

source image → upscale → output

may be perfectly reasonable as a direct model call.

Canberk's POV

If all the layer adds is another network hop, you should not use it.

Use each::workflows when it removes branching, dependency, validation, output, or delivery logic that the application would otherwise have to own. Do not add those things simply to justify having a workflow.

A concrete feed-to-image architecture

Put together, the production path can look like this:

Product feed / PIM
        ↓
Input normalizer
        ↓
Catalog job manifest
        ↓
Variant planner
        ↓
Chunk into bulk submissions
        ↓
Versioned generation workflow
        ↓
Generation / editing
        ↓
Validation
   ┌────┴─────────┐
approved       review/retry
   ↓
Approved product master
   ↓
Destination fan-out
 ┌──────────┬───────────┐
listing   gallery       ads
   ↓
Publish / asset store

The boundaries matter more than the individual tools.

The feed owns product truth

SKU, variant, material, color, source assets, and other factual attributes originate upstream.

The template owns transformation rules

The visual template decides what a particular asset class is allowed to change.

A primary product-detail image and a lifestyle ad should not accidentally inherit the same permissions just because they happen to call the same generation model.

Execution IDs own traceability

For every requested asset, you should be able to answer:

  • which SKU created it?
  • which template and workflow version ran?
  • which execution generated it?
  • what output came back?
  • what validation state did it reach?
  • was it regenerated?
  • where was it eventually published?

Destination branches own publishing rules

Avoid one generic marketplace_ready flag.

An image can be acceptable for one destination and invalid for another. Record those outcomes separately.

Regenerate only what changed

Versioning pays off again during catalog refreshes.

Suppose ad-lifestyle-v5 changes while listing-primary-v3 stays untouched.

The affected set is not every image for every SKU. It is every asset that depends on the changed advertising template.

At catalog scale, being able to answer that dependency question before another model call is much more valuable than making regeneration itself fast.

What to measure after the images ship

Infrastructure metrics matter, but they do not tell you whether the catalog is usable.

Canberk's POV

Instrument the outcome, not just the response.

At asset level, useful fields include:

  • execution success or failure;
  • template/workflow version;
  • end-to-end latency;
  • validation state;
  • regeneration count;
  • human-review state;
  • approved/published state;
  • actual generation cost where available.

At catalog level, ask:

  • What percentage of requested assets reached approval?
  • Which templates create the most review work?
  • Which failure classes cause regeneration?
  • Which categories fail disproportionately?
  • How many outputs reach publication without manual intervention?
  • Which branches consume work without producing assets the business uses?

A very high API success rate can coexist with a catalog that requires constant manual cleanup.

The response tells you whether infrastructure returned something. Acceptance and publication tell you whether the feature worked.

FAQ

What is bulk product image generation?

Bulk product image generation is the automated production of many independently traceable product-image jobs from structured catalog data under shared visual rules. Each SKU or variant should remain separately executable, validatable, retryable, and publishable rather than becoming part of one giant generation request.

Can you generate product images directly from a CSV or product feed?

Yes, but normalize the feed into structured jobs first. Keep fields such as SKU, variant, source image, color, and material as explicit data. Let the visual template define the creative treatment instead of turning every feed row into a different free-form prompt.

How do you keep AI-generated product images consistent across a catalog?

Use versioned templates, structured product data, stable visual controls, and shared validation rules. Consistency should come from the production contract around the model rather than from assuming that similar prompts will always behave the same way.

What happens when some images fail during bulk generation?

Keep successful outputs and isolate failed items. Record why each job failed, then rerun only the product, variant, or branch that has a meaningful chance of succeeding. One failed asset should not force the rest of the catalog to run again.

How do you QA AI-generated product images automatically?

Separate deterministic checks, product-fidelity checks, destination rules, and subjective review. Dimensions and file formats are mechanical. Product color, geometry, logos, and text may need additional signals or review. Whether an ad composition is actually strong remains a product judgment, not a universal quality score.

Should product listings and advertising creative use the same template?

Usually not. They can share the same trusted product source, but listing imagery should use stricter preservation and destination rules. Advertising assets can allow more variation in scene, crop, composition, and context while preserving the underlying SKU.