Examples
Complete integration examples using curl, JavaScript, and Python.
This page walks through a complete photo shoot generation workflow in three languages: curl, JavaScript (fetch), and Python (requests). Each example follows the same steps:
- List your products to pick one for the shoot.
- List approved AI models and sceneries.
- Register a photo shoot generation combining a product, model, and scenery.
- Poll the image-picker endpoint until results are ready.
- Review and approve the generated images.
Common Setup
Every request requires two things:
- Base URL —
https://app.qamera.ai(replace with your own URL for self-hosted deployments). - Authentication — Pass your API key in the
X-Api-Keyheader.
X-Api-Key: mk_live_abc123.secret456
You can generate an API key from Settings > API Keys in your team workspace. The key is shown only once, so copy it immediately.
GET endpoints are free and never consume credits. POST endpoints that trigger content generation reserve credits up front and consume them after processing completes.
curl Examples
Step 1 — List Products
Retrieve all products uploaded to your account.
curl -s -X GET "https://app.qamera.ai/api/external/products" \ -H "X-Api-Key: mk_live_abc123.secret456"
Response:
{
"products": [
{
"id": "rec_product_001",
"name": "White T-Shirt Front",
"thumbnail": "https://cdn.example.com/products/thumb_001.jpg",
"status": "UPLOADED",
"createdAt": "2026-03-20T14:30:00.000Z"
},
{
"id": "rec_product_002",
"name": "Leather Handbag",
"thumbnail": "https://cdn.example.com/products/thumb_002.jpg",
"status": "UPLOADED",
"createdAt": "2026-03-21T09:15:00.000Z"
}
],
"count": 2
}
Pick a product ID to use in the photo shoot. In these examples we will use rec_product_001.
Step 2 — List Approved Models
Fetch only models that have been approved for use.
curl -s -X GET "https://app.qamera.ai/api/external/models?statusFilter=approved" \ -H "X-Api-Key: mk_live_abc123.secret456"
Response:
{
"models": [
{
"id": "rec_model_101",
"name": "Sophie — Casual",
"thumbnail": "https://cdn.example.com/models/thumb_101.jpg",
"status": "DONE",
"voting": "APPROVED",
"createdAt": "2026-03-18T10:00:00.000Z"
}
],
"count": 1
}
Step 3 — List Approved Sceneries
curl -s -X GET "https://app.qamera.ai/api/external/sceneries?statusFilter=approved" \ -H "X-Api-Key: mk_live_abc123.secret456"
Response:
{
"sceneries": [
{
"id": "rec_scenery_201",
"name": "Bright Studio",
"thumbnail": "https://cdn.example.com/sceneries/thumb_201.jpg",
"status": "DONE",
"voting": "APPROVED",
"createdAt": "2026-03-19T11:45:00.000Z"
}
],
"count": 1
}
Step 4 — Register a Photo Shoot
Combine the product, model, and scenery to start a generation. This endpoint reserves credits.
curl -s -X POST "https://app.qamera.ai/api/external/image-picker/register-ideas" \
-H "X-Api-Key: mk_live_abc123.secret456" \
-H "Content-Type: application/json" \
-d '{
"config": {
"product": { "id": "rec_product_001" },
"model": { "id": "rec_model_101" },
"scenery": { "id": "rec_scenery_201" },
"industry": "fashion",
"suggestions": "bright lighting, clean composition"
},
"count": 4
}'
Response:
{
"createdRecordIds": ["rec_gen_301", "rec_gen_302", "rec_gen_303", "rec_gen_304"],
"batchId": "batch_abc123",
"orderId": "ord_xyz789",
"reservationId": "res_def456",
"creditsReserved": 120
}
Save the createdRecordIds — you will use them to check progress.
Step 5 — Poll for Results
Generation is asynchronous. Poll the image-picker endpoint, filtering by your product, until images move to the DONE status.
curl -s -X GET "https://app.qamera.ai/api/external/image-picker?productId=rec_product_001" \ -H "X-Api-Key: mk_live_abc123.secret456"
Response (while still processing):
{
"images": [
{
"id": "rec_gen_301",
"name": "White T-Shirt — Variant 1",
"thumbnail": "",
"status": "INPROGRESS",
"voting": null,
"createdAt": "2026-03-27T12:00:00.000Z"
}
],
"count": 1
}
Wait a minute and call the same endpoint again. Once status is DONE, the thumbnail field contains the image URL.
Step 6 — Approve or Reject Results
You can approve images using the packshot update endpoint. For photo shoot images that you want to keep, approve them so they appear in filtered views.
# Approve an image
curl -s -X POST "https://app.qamera.ai/api/external/packshots/update-packshot" \
-H "X-Api-Key: mk_live_abc123.secret456" \
-H "Content-Type: application/json" \
-d '{
"recordId": "rec_gen_301",
"Voting": "APPROVED"
}'
Response:
{
"success": true,
"message": "Packshot voting updated successfully",
"recordId": "rec_gen_301"
}
To reject an image (which archives it):
curl -s -X POST "https://app.qamera.ai/api/external/packshots/update-packshot" \
-H "X-Api-Key: mk_live_abc123.secret456" \
-H "Content-Type: application/json" \
-d '{
"recordId": "rec_gen_302",
"Voting": "REJECTED"
}'
JavaScript (fetch) Example
The following example uses the browser/Node.js fetch API to perform the same workflow.
Helper Function
const BASE_URL = "https://app.qamera.ai";
const API_KEY = "mk_live_abc123.secret456";
async function qameraFetch(path, options = {}) {
const url = new URL(path, BASE_URL);
if (options.params) {
Object.entries(options.params).forEach(([key, value]) =>
url.searchParams.set(key, value)
);
}
const response = await fetch(url.toString(), {
method: options.method || "GET",
headers: {
"X-Api-Key": API_KEY,
...(options.body ? { "Content-Type": "application/json" } : {}),
},
...(options.body ? { body: JSON.stringify(options.body) } : {}),
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(
`API error ${response.status}: ${error.error || response.statusText}`
);
}
return response.json();
}
Full Workflow
async function runPhotoShootWorkflow() {
// 1. List products
const { products } = await qameraFetch("/api/external/products");
console.log(`Found ${products.length} products`);
const product = products[0];
if (!product) throw new Error("No products found. Upload one first.");
console.log(`Using product: ${product.name} (${product.id})`);
// 2. List approved models
const { models } = await qameraFetch("/api/external/models", {
params: { statusFilter: "approved" },
});
console.log(`Found ${models.length} approved models`);
// 3. List approved sceneries
const { sceneries } = await qameraFetch("/api/external/sceneries", {
params: { statusFilter: "approved" },
});
console.log(`Found ${sceneries.length} approved sceneries`);
// 4. Register photo shoot generation
const registration = await qameraFetch(
"/api/external/image-picker/register-ideas",
{
method: "POST",
body: {
config: {
product: { id: product.id },
model: models[0] ? { id: models[0].id } : undefined,
scenery: sceneries[0] ? { id: sceneries[0].id } : undefined,
industry: "fashion",
suggestions: "bright lighting, clean composition",
},
count: 4,
},
}
);
console.log(`Generation started — order ${registration.orderId}`);
console.log(`Credits reserved: ${registration.creditsReserved}`);
// 5. Poll until results are ready
const generatedIds = new Set(registration.createdRecordIds);
let attempts = 0;
const maxAttempts = 30;
while (attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 10_000));
attempts++;
const { images } = await qameraFetch("/api/external/image-picker", {
params: { productId: product.id },
});
const ours = images.filter((img) => generatedIds.has(img.id));
const done = ours.filter((img) => img.status === "DONE");
console.log(`Poll ${attempts}: ${done.length}/${ours.length} done`);
if (done.length === ours.length && done.length > 0) {
console.log("All images ready!");
// 6. Approve the first result
await qameraFetch("/api/external/packshots/update-packshot", {
method: "POST",
body: {
recordId: done[0].id,
Voting: "APPROVED",
},
});
console.log(`Approved image ${done[0].id}`);
return done;
}
}
throw new Error("Generation timed out after 5 minutes");
}
runPhotoShootWorkflow()
.then((images) => console.log("Completed:", images))
.catch((err) => console.error("Failed:", err.message));
Python (requests) Example
The following example uses the requests library (pip install requests).
Session Setup
import time
import requests
BASE_URL = "https://app.qamera.ai"
API_KEY = "mk_live_abc123.secret456"
session = requests.Session()
session.headers.update({"X-Api-Key": API_KEY})
Full Workflow
def run_photo_shoot_workflow():
# 1. List products
resp = session.get(f"{BASE_URL}/api/external/products")
resp.raise_for_status()
products = resp.json()["products"]
print(f"Found {len(products)} products")
if not products:
raise RuntimeError("No products found. Upload one first.")
product = products[0]
print(f"Using product: {product['name']} ({product['id']})")
# 2. List approved models
resp = session.get(
f"{BASE_URL}/api/external/models",
params={"statusFilter": "approved"},
)
resp.raise_for_status()
models = resp.json()["models"]
print(f"Found {len(models)} approved models")
# 3. List approved sceneries
resp = session.get(
f"{BASE_URL}/api/external/sceneries",
params={"statusFilter": "approved"},
)
resp.raise_for_status()
sceneries = resp.json()["sceneries"]
print(f"Found {len(sceneries)} approved sceneries")
# 4. Register photo shoot generation
config = {
"product": {"id": product["id"]},
"industry": "fashion",
"suggestions": "bright lighting, clean composition",
}
if models:
config["model"] = {"id": models[0]["id"]}
if sceneries:
config["scenery"] = {"id": sceneries[0]["id"]}
resp = session.post(
f"{BASE_URL}/api/external/image-picker/register-ideas",
json={"config": config, "count": 4},
)
resp.raise_for_status()
registration = resp.json()
print(f"Generation started — order {registration['orderId']}")
print(f"Credits reserved: {registration['creditsReserved']}")
# 5. Poll until results are ready
generated_ids = set(registration["createdRecordIds"])
max_attempts = 30
for attempt in range(1, max_attempts + 1):
time.sleep(10)
resp = session.get(
f"{BASE_URL}/api/external/image-picker",
params={"productId": product["id"]},
)
resp.raise_for_status()
images = resp.json()["images"]
ours = [img for img in images if img["id"] in generated_ids]
done = [img for img in ours if img["status"] == "DONE"]
print(f"Poll {attempt}: {len(done)}/{len(ours)} done")
if len(done) == len(ours) and done:
print("All images ready!")
# 6. Approve the first result
resp = session.post(
f"{BASE_URL}/api/external/packshots/update-packshot",
json={
"recordId": done[0]["id"],
"Voting": "APPROVED",
},
)
resp.raise_for_status()
print(f"Approved image {done[0]['id']}")
return done
raise TimeoutError("Generation timed out after 5 minutes")
if __name__ == "__main__":
try:
results = run_photo_shoot_workflow()
for img in results:
print(f" {img['name']}: {img['thumbnail']}")
except Exception as e:
print(f"Error: {e}")