Bulk create
Generate images for multiple products with the same settings
Bulk Create starts one job per tag, using the same instructions and generation settings for every product. Use it to create on-model images or product packshots for a collection without setting up each job separately.
You can select up to 100 tags per run, with 1 to 10 input images per job. Each tag should group the views of one product.
Quick start
Upload files or folders and group each product's images under a tag. If your images are already tagged in the Asset Library, skip the upload step. See Organizing products for details.
Select the tags to process and choose the input views for each product. Tags with no usable images are skipped; tags with more than 10 images need a smaller selection.
Choose Flat to Model or Create Packshot. For Flat to Model, select an identity from the library or your own identities. The same identity is used for every job in the run.
Choose a template or load a preset, then fine-tune the instructions and output settings. These settings apply to all selected tags. See Shared settings for details.
Create a new project POST
/projectDry-run a bulk generation POST
/bulk-jobs/previewStart a bulk generation POST
/bulk-jobsChoose a destination project, review the job count, outputs, and estimated credits, then resolve any blockers before starting. See Reviewing and starting a run for details.
Follow the jobs from the Jobs page, or check each returned job ID through the API. See Tracking progress and results for details.
Uploading images does not start generation. Jobs start only after you review and confirm the run.
Organizing products
Use a separate tag for each product, such as blazer-navy or shirt-white. Put front, back, side, and detail views of the same product under that tag. Avoid grouping unrelated products together: the selected images are combined as inputs to one job.
- Select up to 100 tags in one run.
- Use all images in a tag, or pick a subset of 1 to 10 views.
- If a tag contains more than 10 images, choose which views to use before starting.
- If the images in a tag change after you make a selection, review and pick the images again.
Existing tags and images can be reused across runs. You do not need to upload the same product photos again.
Shared settings
Choose one job type for the whole run:
| Job type | What you get | Identity required |
|---|---|---|
| Flat to Model | A model wearing the garment, using your chosen identity. | Yes, one identity for all jobs. |
| Create Packshot | Product-only images, such as flat-lays, ghost-mannequin shots, or white-background packshots. | No. |
If you need both on-model images and packshots, create separate runs. See Flat lay on model and Create packshot for instruction parameters and output options.
Each instruction produces one output per job by default. Set num_variations to request more outputs from an instruction. For example, 20 eligible tags with two instructions and one variation per instruction produce 20 jobs and 40 output images.
Increasing the number of instructions, variations, or output resolution increases the estimated credit cost. Check the preview before starting rather than estimating from the tag count alone.
Reviewing and starting a run
The review shows the destination project, eligible tags, input selections, instructions, total outputs, and estimated credits. Check the credits required for the run alongside those already reserved by running jobs and your available balance.
Resolve any warnings about image selections, credits, or output sizes before starting. Your account's running-job limit also applies to bulk runs. If there is not enough capacity, wait for existing jobs to finish or select fewer tags.
Tags with no usable images do not start a job. If only part of a run starts, the remaining tags stay in the draft so you can review and retry them without restarting successful jobs.
Starting the same tags again creates new jobs. After a partial start, retry only the tags that did not start to avoid duplicate work and additional credit usage.
Using the API
The following examples create product tags, upload their images, preview a packshot run, and start one job per eligible tag. If you already have a project and tagged assets, skip to Previewing a run.
You will need an API token to send HTTP requests. See Authentication for instructions. The Python examples below are intended to be run in order.
Creating a project
Create a destination project, or use an existing project_id.
import requests
api_url = "https://v2.api.piktid.com"
access_token = "your_access_token"
headers = {"Authorization": "Bearer " + access_token}
response = requests.post(
api_url + "/project",
headers=headers,
json={"project_name": "spring-collection"},
)
response.raise_for_status()
project_id = response.json()["project_id"]Creating tags and uploading images
Create a tag with POST /tags, then assign it when requesting an upload URL. Complete the image upload with a PUT request using the returned content_type. Keep the file_id values if you want to choose specific input views later.
The upload_url is only valid for a limited time. Upload the image immediately after receiving the response, and complete all uploads before previewing or starting a run.
from pathlib import Path
products = {
"blazer-navy": ["images/blazer-front.jpg", "images/blazer-back.jpg"],
"shirt-white": ["images/shirt-front.jpg", "images/shirt-back.jpg"],
}
tag_ids = []
image_selections = {}
for name, image_paths in products.items():
response = requests.post(
api_url + "/tags",
headers=headers,
json={"name": name, "color": "#2E5A3B"},
)
response.raise_for_status()
tag_id = response.json()["id"]
tag_ids.append(tag_id)
image_selections[tag_id] = []
for image_path in image_paths:
response = requests.post(
api_url + "/upload",
headers=headers,
json={"filename": Path(image_path).name, "tag_ids": [tag_id]},
)
response.raise_for_status()
upload = response.json()
with open(image_path, "rb") as image:
response = requests.put(
upload["upload_url"],
headers={"Content-Type": upload["content_type"]},
data=image,
)
response.raise_for_status()
image_selections[tag_id].append(upload["file_id"])Previewing a run
Send the shared settings to POST /bulk-jobs/preview to check tag eligibility and estimated credits without starting jobs. For existing assets, replace tag_ids and image_selections below with your own tag IDs and uploaded file IDs.
bulk_request = {
"job_type": "create_packshot",
"project_id": project_id,
"tag_ids": tag_ids,
"image_selections": image_selections,
"instructions": [
{
"style": "white_cutout",
"angle": "front",
"num_variations": 1,
"options": {"size": "2K", "ar": "3:4", "format": "jpg"},
},
],
}
response = requests.post(
api_url + "/bulk-jobs/preview",
headers=headers,
json=bulk_request,
)
response.raise_for_status()
preview = response.json()
print(f"Eligible jobs: {preview['eligible_count']}")
print(f"Estimated credits: {preview['credits']['total']}")
if preview["blockers"]:
raise RuntimeError(preview["blockers"]){
"tags": [
{
"tag_id": "tag_blazer...",
"name": "blazer-navy",
"image_count": 2,
"selected_count": 2,
"eligible": true,
"reason": null
},
{
"tag_id": "tag_shirt...",
"name": "shirt-white",
"image_count": 2,
"selected_count": 2,
"eligible": true,
"reason": null
}
],
"eligible_count": 2,
"credits": {
"per_job": 5,
"total": 10, // Estimated credits for eligible jobs
"in_progress": 0,
"available": 100
},
"blockers": []
}image_selections is optional for tags with 1 to 10 usable images. Omit a tag's entry to use all its images, or supply a list of uploaded file_id values to choose a subset. Every selection must belong to its tag and contain 1 to 10 unique file IDs.
The preview returns blockers for image selections that need picking, insufficient credits, output sizes outside your account's limits, or a run with no eligible tags. An invalid selection, such as an image no longer belonging to its tag, returns an error; update the selection and preview again.
Starting a run
Send the reviewed request to POST /bulk-jobs. A destination project_id is required. Keep each returned job_id together with its tag_id so you can match jobs to products.
response = requests.post(
api_url + "/bulk-jobs",
headers=headers,
json=bulk_request,
)
response.raise_for_status()
created = response.json()
for job in created["jobs"]:
print(f"Tag {job['tag_id']}: started job {job['job_id']}")
print("Skipped tags:", created["skipped"])
print("Jobs that failed to start:", created["failed"]){
"jobs": [
{"job_id": "job_blazer...", "tag_id": "tag_blazer..."},
{"job_id": "job_shirt...", "tag_id": "tag_shirt..."}
],
"skipped": [],
"failed": []
}jobs lists jobs that started, skipped lists tags that were not eligible with a reason, and failed lists jobs that failed to start. Review all three lists before retrying. A successful HTTP response does not mean every selected tag started, or that image generation has finished.
A preview does not reserve credits or running-job capacity. The start request checks your current limits again and can be rejected if your balance or available capacity has changed.
Creating on-model images
For Flat to Model, use job_type: "flat_2_model", provide one identity_code, and replace the packshot instructions with flat-to-model instructions. Use this request with the same preview and start endpoints.
bulk_request = {
"job_type": "flat_2_model",
"project_id": project_id,
"tag_ids": tag_ids,
"image_selections": image_selections,
"identity_code": "your_identity_code",
"instructions": [
{
"pose": "standing front-facing",
"background": "white studio",
"lighting": "soft",
"camera": {"framing": "full body", "angle": "eye level"},
"options": {"size": "2K", "ar": "3:4", "format": "jpg"},
},
],
}See Uploading identities to create an identity or select an existing one.
Tracking progress and results
Each product has its own job status and results. In the app, open the Jobs page to follow progress and review the outputs in your destination project.
With the API, check each returned job ID separately:
for job in created["jobs"]:
job_id = job["job_id"]
response = requests.get(
api_url + f"/jobs/{job_id}/status",
headers=headers,
)
response.raise_for_status()
status = response.json()
print(f"Tag {job['tag_id']}: {status['status']} ({status['progress']}%)")
if status["status"] == "completed":
response = requests.get(
api_url + f"/jobs/{job_id}/results",
headers=headers,
)
response.raise_for_status()
results = response.json()
print(results["results"])This example checks progress once. You can also use SSE or webhooks for ongoing updates, matching notifications against the returned job IDs. See Tracking progress for examples.