Packshot from a photo
Step by step — from an ordinary shop photo, through packshot candidates and accepting one of them, to ordering a photo session.
What you'll achieve
The merchant only has an ordinary product photo — from a phone, from the warehouse, on any background. In this tutorial you'll upload that photo, order a few packshot candidates, accept the chosen one on the merchant's behalf, and order the first photo session.
Along the way you'll learn the most important rule of this API: a photo session can only be ordered for a product with an approved packshot. A session for a product without an approved packshot returns 422 packshot_not_approved.
Prerequisites
- A plugin installation API key (how to get one) with these permissions:
plugin.assets:upload,plugin.catalog:write,plugin.jobs:create,plugin.jobs:read. - An ordinary product photo (JPEG/PNG/WebP, up to 50 MB).
In the examples, replace mk_live_… with your own key. The base address is https://qamera.ai.
Flow
1. POST /assets/upload + PUT → upload the shop photo
2. POST /images → register the photo in the catalog
3. POST /jobs (job_type=packshot) → order N packshot candidates
4. POST /jobs/{id}/accept → accept the chosen candidate
POST /jobs/{id}/reject → reject the rest
5. POST /jobs → order a photo session
Steps
1. Upload the shop photo
Same as in tutorial A: get a temporary URL and send the file.
curl -X POST https://qamera.ai/api/v1/plugin/assets/upload \
-H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" \
-H "Content-Type: application/json" \
-d '{
"mode": "presigned",
"filename": "kubek-z-telefonu.jpg",
"content_type": "image/jpeg",
"size_bytes": 1893421
}'
curl -X PUT "$UPLOAD_URL" \
--upload-file kubek-z-telefonu.jpg \
-H "Content-Type: image/jpeg"
Save the asset_id from the first response — you'll use it in steps 2 and 3.
2. Register the photo in the catalog
Registration creates the product (if product_ref is new) and attaches the source photo to it. After registration the photo is analyzed automatically — the analysis describes the product and improves generation quality.
curl -X POST https://qamera.ai/api/v1/plugin/images \
-H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"external_ref": "sklep1:zdjecie-101",
"product_ref": "sklep1:produkt-7",
"product_metadata": { "display_name": "Kubek ceramiczny 300 ml" },
"asset_id": "9a3b8f4c-2e7a-4c1b-8d0a-1f6c2e9b3d51"
}
]
}'
The response (HTTP 200) contains product_id, image_id, and status: "created".
You can check the analysis state in the photo's analysis_status field (GET /products/sklep1:produkt-7 returns the product with its embedded photos). Before you order generation, wait until it reaches described — usually a dozen or so seconds.
3. Order packshot candidates
Now you order generation: job_type: "packshot", and in the session product:
packshot_asset_id— here: theasset_idof the shop photo from step 1 (this is the raw input material the packshot will be created from),auto_register_packshot: true— the generated candidate will land automatically in the product catalog (both fields are required withjob_type: "packshot"),images_count— how many candidates to generate (the merchant will have something to choose from).
curl -X POST https://qamera.ai/api/v1/plugin/jobs \
-H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" \
-H "Idempotency-Key: sklep1-packshoty-produkt-7" \
-H "Content-Type: application/json" \
-d '{
"job_type": "packshot",
"session_config": { "aspect_ratio": "1:1" },
"subjects": [
{
"packshot_asset_id": "9a3b8f4c-2e7a-4c1b-8d0a-1f6c2e9b3d51",
"product_label": "Kubek ceramiczny 300 ml",
"product_ref": "sklep1:produkt-7",
"images_count": 3,
"ai_model": "byteplus/seedream-4.5",
"auto_register_packshot": true,
"packshot_external_ref": "sklep1:packshot-kandydat"
}
]
}'
The response (HTTP 201) contains order_id and three job_ids — one per candidate. Save the job_ids; that's what you'll use to accept or reject the candidates in step 4.
Receive the generated candidates (webhook or GET /jobs/{id} — see receiving results) and show them to the merchant to choose from.
4. Accept the chosen candidate, reject the rest
Two meanings of acceptance. Accepting a packshot job has an effect in the catalog: the generated packshot becomes the product's approved packshot and unblocks photo sessions. Accepting a photo_shoot job (session images) is purely feedback — it changes nothing. Same endpoint, different effect — the job type decides.
# the candidate the merchant chose curl -X POST https://qamera.ai/api/v1/plugin/jobs/00000000-0000-0000-0000-0000000000b1/accept \ -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" # the remaining candidates curl -X POST https://qamera.ai/api/v1/plugin/jobs/00000000-0000-0000-0000-0000000000b2/reject \ -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" curl -X POST https://qamera.ai/api/v1/plugin/jobs/00000000-0000-0000-0000-0000000000b3/reject \ -H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy"
Each call returns 204 No Content. From the moment of acceptance, the product sklep1:produkt-7 has an approved packshot — the requirement for photo sessions is met.
For comparison: a packshot uploaded directly via POST /packshots (as in tutorial A) is approved automatically. Generated candidates require explicit acceptance, because it's the merchant who decides which one is good.
5. Order a photo session
Exactly as in tutorial A — omit packshot_asset_id and the session will use the product's latest approved packshot (the one from step 4):
curl -X POST https://qamera.ai/api/v1/plugin/jobs \
-H "X-Api-Key: mk_live_xxxxxxxx.yyyyyyyy" \
-H "Idempotency-Key: sklep1-sesja-produkt-7" \
-H "Content-Type: application/json" \
-d '{
"session_config": { "aspect_ratio": "4:5" },
"subjects": [
{
"product_label": "Kubek ceramiczny 300 ml",
"product_ref": "sklep1:produkt-7",
"images_count": 4,
"ai_model": "byteplus/seedream-4.5"
}
]
}'
If you had sent this request before the acceptance in step 4, you'd get 422 packshot_not_approved — and that's the expected behavior, not an integration error.
Common errors
| Error | Why it happened | What to do |
|---|---|---|
422 packshot_not_approved | No candidate has been accepted yet (or all were rejected) | Accept one candidate (POST /jobs/{id}/accept) and retry the session — details |
400 invalid_input at step 3 | Missing auto_register_packshot: true or packshot_asset_id with job_type: "packshot" | Provide both fields — they're required for packshot generation — details |
400 invalid_input at step 2 | A new product_ref without product_metadata.display_name | Add product_metadata with the product name — details |
409 idempotency_conflict | The same external_ref but different file content (duplicate by checksum) | Use a new external_ref for a new file — details |
409 job_not_completed | Accept/reject before the candidate finished generating | Wait for status: "completed" — details |
402 quota_exceeded | Not enough credits for generation | The merchant needs to top up credits — details |
Next steps
- Session from a packshot — the shorter path, when the packshot already exists.
- Session parameters — give the merchant a choice of style and scenery.
- Repeat session — more images in the same style.