Case-study projects

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

TestAcceptance criterion
Draft generationImage matches the brief and is reviewed before publication.
Reference editA reviewer checks product fidelity; a prompt does not guarantee exact preservation.
Missing session--output fails before generation; set the owner’s app session.
Repeated requestReplay the same body and ID; verify idempotent replay rather than creating another image.
Private deliveryNo 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.