Batches
A batch is a draft until you start it: you can add photos in as many requests as you like and nothing is charged. Starting reserves one credit per photo. Every answer below is the same JSON Job object: status, counts, the settings, one entry per photo with its file links, and an estimate of the time left.
1. Create
POST/v1/jobs
curl -X POST https://trycutstack.com/v1/jobs \
-H "Authorization: Bearer $CUTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "spring catalogue",
"defaults": { "presetId": "amazon-main", "background": { "mode": "white" } }
}'defaults is the setting every photo starts with; every field has a default, so {} is a valid body. Pass webhookUrl to be told about progress (see Events). For a background image, send multipart instead: options (the same JSON) and a background file part.
The answer is 201 with the draft. Keep its id.
2. Add photos
POST/v1/jobs/{id}/items
Files, as multipart parts named files: JPEG, PNG, WebP, or a ZIP of them (folders inside are fine).
curl -X POST https://trycutstack.com/v1/jobs/$JOB/items \
-H "Authorization: Bearer $CUTSTACK_API_KEY" \
-F "files=@shoe-1.jpg" -F "files=@shoe-2.jpg" -F "files=@rest.zip"Or links, as JSON. We fetch them server-side over https from public hosts; each link becomes a photo named after the last part of its path.
curl -X POST https://trycutstack.com/v1/jobs/$JOB/items \
-H "Authorization: Bearer $CUTSTACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "urls": ["https://cdn.example.com/p/shoe-1.jpg", "https://cdn.example.com/p/shoe-2.jpg"],
"zipUrl": "https://cdn.example.com/p/rest.zip" }'The answer carries added and the batch. Anything refused is listed in rejected with a reason (not an image, over 25 MB, over the 200-photo limit, a link that answered 404) and is never silently dropped. Once the batch is started, adding photos answers 409 NOT_DRAFT: start another batch.
3. Start
POST/v1/jobs/{id}/start
curl -X POST https://trycutstack.com/v1/jobs/$JOB/start \
-H "Authorization: Bearer $CUTSTACK_API_KEY" \
-H "Idempotency-Key: catalogue-2026-09-14" \
-H "Content-Type: application/json" \
-d '{ "sample": 10 }'- Idempotency-Key: any 8–128 characters. If the network drops after we answered, retrying with the same key replays the first answer instead of starting (and charging) twice. Remembered for 24 hours. Use it on every start.
- sample: run only a spread sample of N photos now and hold the rest. Check the results, then
POST /continueto run the others (charged then), or delete the batch. The studio calls this “Try 10 first”. - defaults: replace the settings the draft was created with, for every photo, right before running.
202 means queued. 402 NO_CREDITS tells you what is missing; 409 TOO_MANY_ACTIVE means you already have the maximum number of batches running for this account (two): wait for one, or stop it.
4. Follow it
GET/v1/jobs/{id}?wait=60&since=<updatedAt>
One request instead of a polling loop: wait (1–60 seconds) holds the answer until something changes after since, until the batch stops running, or until the time is up. Pass the updatedAt of the last answer as since and you receive only the photos that changed (delta: true); merge them by id. Add fields=summary to get counts and status without the photos at all.
SINCE=0
while :; do
J=$(curl -s "https://trycutstack.com/v1/jobs/$JOB?wait=60&since=$SINCE" -H "Authorization: Bearer $CUTSTACK_API_KEY")
echo "$J" | jq -c '{status, done: .counts.done, failed: .counts.failed, left: .estimate.remainingMs}'
case "$(echo "$J" | jq -r .status)" in queued|running) SINCE=$(echo "$J" | jq .updatedAt) ;; *) break ;; esac
doneStatuses: draft, queued, running, paused (queued photos finished, some still held: after a sample run or a stop), done, failed. If you would rather be told than ask, open the event stream or set a webhook: Events and webhooks.
5. Download
GET/v1/jobs/{id}/download
A streamed ZIP of every finished photo. The query shapes it:
| Parameter | What it does |
|---|---|
pattern | File names. Tokens: {name} {index} {n} {preset} {w} {h} {date} {sku}. Default {name}. |
format | png, jpeg or webp: convert every file. Default: as rendered. |
optimize | marketplace (default) keeps each file under the preset’s upload limit; lossless, web; off = as rendered, fastest, and the ZIP announces its exact size. |
include | Comma-separated rendered, cutouts (transparent PNGs), originals. More than one kind = a folder each. |
presets | Also render every photo for these presets, a folder per preset. One batch, several marketplaces. |
items | Comma-separated photo ids, in the order you want. Default: every finished photo. |
skus | A JSON array of SKUs aligned with items, for the {sku} token. |
start | First {index}. Default 1. |
curl "https://trycutstack.com/v1/jobs/$JOB/download?pattern={sku}-{preset}&presets=etsy,shopify&items=$IDS&skus=$SKUS" \
-H "Authorization: Bearer $CUTSTACK_API_KEY" -o catalogue.zipOne photo at a time: every photo in the batch answer has outputUrl, cutoutUrl, thumbUrl and inputUrl, all under /v1/jobs/{id}/items/{itemId}/file. Append &format=png to convert, &download=1 for an attachment. Send the same key: there are no public links.
Stopping, resuming, deleting
POST /v1/jobs/{id}/cancelstops a running batch: photos not started go back to waiting and their credits are released at once; photos already at the model finish. The batch ends uppaused.POST /v1/jobs/{id}/continueruns the held photos of a sample run or a stopped batch. This is where the rest is charged.DELETE /v1/jobs/{id}deletes a batch now instead of after two hours; open reservations are released. A running batch must be stopped first.GET /v1/jobs?limit=20&status=done&before=<createdAt>lists your batches, newest first, without photos.
A batch and every file in it is deleted about two hours after it finishes. Download first, then do whatever you do with the files.
Next: Settings and presets. Sizes, backgrounds, what is free and what runs the model.