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_idhere: the asset_id of 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 with job_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

ErrorWhy it happenedWhat to do
422 packshot_not_approvedNo 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 3Missing 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 2A new product_ref without product_metadata.display_nameAdd product_metadata with the product name — details
409 idempotency_conflictThe same external_ref but different file content (duplicate by checksum)Use a new external_ref for a new file — details
409 job_not_completedAccept/reject before the candidate finished generatingWait for status: "completed"details
402 quota_exceededNot enough credits for generationThe merchant needs to top up credits — details

Next steps