Project: product image studio
Generate and edit product artwork, retrieve its private bytes, and retain usage receipts.
Scenario and outcome
An illustrative product team needs draft artwork for a product page. Build a command-line studio that generates one image, optionally edits a reference, and saves the result for human review. This tutorial makes no claim about actual customer results.
Flow: creative brief → Kendr image route → private image metadata → authenticated download → visual review.
1. Prepare credentials and files
Complete the API quickstart, fund the account, and create a key with models:invoke. Set the key in the shell where you run the project:
# macOS / Linux export KENDR_API_KEY="kndr_live_..." # PowerShell $env:KENDR_API_KEY="kndr_live_..."
Each command requires a request ID. Use a new ID when the input changes and preserve it for the same operation’s recovery. Calls consume account credits. The examples make one attempt and do not silently retry chargeable calls.
For downloads, also obtain an app session for the same account using app session authentication and set KENDR_SESSION_TOKEN. An API key can generate the image but cannot retrieve its bytes.
# macOS / Linux export KENDR_SESSION_TOKEN="YOUR_APP_SESSION_TOKEN" # PowerShell $env:KENDR_SESSION_TOKEN="YOUR_APP_SESSION_TOKEN"
Use reference images you are authorized to process. The script accepts PNG, JPEG, or WebP and lets the API verify the bytes.
2. Save the implementation
Download image_studio.py and kendr_http.py into the same folder. These scripts use Python 3.10+ and only the standard library.
"""Generate an image; optionally save its bytes using the same owner's app session."""
import argparse
import base64
import json
import os
from pathlib import Path
import re
from urllib.request import Request, urlopen
from kendr_http import API_ORIGIN, post_json
def generate(prompt, request_id, reference=None):
payload = {"model": "kendr-image", "prompt": prompt,
"size": "1536x1024", "quality": "medium"}
if reference:
source = Path(reference)
if source.stat().st_size > 8 * 1024 * 1024:
raise ValueError("Reference image exceeds 8 MiB")
# The server infers and validates PNG/JPEG/WebP from the bytes.
payload["input_images"] = [{"name": source.name,
"data": base64.b64encode(source.read_bytes()).decode("ascii")}]
return post_json("/api/v1/images/generations", payload, request_id)
def download(image, destination):
# Construct a fixed-origin path; never send the session credential to an arbitrary URL.
image_id = image["id"]
if not re.fullmatch(r"img_[a-f0-9]{24}", image_id):
raise ValueError("Unexpected image identifier")
request = Request(API_ORIGIN + "/api/me/generated-images/" + image_id,
headers={"X-Kendr-Session": os.environ["KENDR_SESSION_TOKEN"]})
with urlopen(request, timeout=180) as response:
content_type = response.headers.get_content_type()
extension = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}.get(content_type)
if not extension:
raise ValueError("Download did not return an image")
path = Path(destination).with_suffix(extension)
with path.open("xb") as output:
output.write(response.read())
return path
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("prompt")
parser.add_argument("--request-id", required=True)
parser.add_argument("--reference", help="Local PNG, JPEG, or WebP to edit")
parser.add_argument("--output", help="Output filename; requires KENDR_SESSION_TOKEN")
args = parser.parse_args()
if args.output and not os.environ.get("KENDR_SESSION_TOKEN"):
parser.error("--output requires KENDR_SESSION_TOKEN for the same account as the API key")
result = generate(args.prompt, args.request_id, args.reference)
print(json.dumps({"request_id": result["request_id"], "image": result["image"],
"credit_micros_charged": result.get("credit_micros_charged")}, indent=2))
if args.output:
print(download(result["image"], args.output))
3. Generate and download
python image_studio.py "A ceramic travel mug on a warm studio background" --request-id mug-hero-v1 --output mug-hero.png
Expected result: the console prints private image metadata and the exact microcredit charge, then writes an image file. The extension follows the returned MIME type. Existing files are not overwritten. Omit --output to generate metadata only.
4. Edit the generated picture
python image_studio.py "Keep the mug shape; change the background to pale blue" --reference mug-hero.png --request-id mug-hero-v2 --output mug-blue.png
Use the actual filename from the previous command if its extension differs. A new revision gets a new request ID and charge. Visually inspect whether the product details, logo, and requested change were preserved.
5. Check quality and failure behavior
| Test | Acceptance criterion |
|---|---|
| Draft generation | Image matches the brief and is reviewed before publication. |
| Reference edit | A reviewer checks product fidelity; a prompt does not guarantee exact preservation. |
| Missing session | --output fails before generation; set the owner’s app session. |
| Repeated request | Replay the same body and ID; verify idempotent replay rather than creating another image. |
| Private delivery | No API or session token appears in URLs, public markup, or logs. |
Store image IDs, request IDs, revisions, and credit_micros_charged in your project database. An image URL remains private. If a download fails after generation, reuse the original ID and unchanged prompt to recover rather than buying a new image. For a production gallery, enforce user authorization on your server and serve only approved outputs.