Wedding Batch Studio, from sign-in to delivery ZIP

Follow the exact browser workflow a photographer sees, then build the same tested project lifecycle in Python. The video and screenshots show real processing and real output files.

Live contract verified · 29 checks · 23 August 2026

Before you start

Camera RAW is not an accepted upload format. Develop and export your selected RAW files first. Keep cropping, masks, blemish work, and hero-image retouching in your normal editor.

Part 1: use the browser

Step 1 — Sign in.
Open pixelapi.dev/app and use the normal PixelAPI sign-in. Once the dashboard opens, choose Wedding Batch Studio under Studios in the left menu.
Step 2 — Name the project.
Use the couple, date, or your internal job number. A specific name is useful when you resume later or keep several weddings active.
Step 3 — Choose the finish.
Natural Warm gently warms the gallery. True Color keeps the treatment restrained. Cinematic Soft adds a softer, slightly moodier finish. Reference Match uses the color character of one approved reference photograph.
Step 4 — Add the reference, if needed.
When Reference Match is selected, choose the approved reference first. The studio will not start a Reference Match project without it.
Step 5 — Select source photographs.
Choose the culled gallery. The browser uploads files sequentially instead of holding the entire batch in memory. Each file receives a stable identity, so the same upload can be resumed without duplicates.
Step 6 — Start processing.
Confirm the source count, then click Start processing. The project shows total, completed, remaining, failed, and cancelled counts plus overall progress.

Part 2: review the output and deliver it

Do not jump straight from 100% to client delivery. Open representative images from every lighting group—getting ready, ceremony, portraits, reception, and dance floor. Batch finishing is a base pass; emotionally important or difficult frames may still deserve individual work.

Finished ceremony portrait
Finished ceremony portrait · actual API result
Finished couple portrait
Finished couple portrait · actual API result

When the review is clean, download the project ZIP. It contains a manifest.json and a finished/ folder with ordered JPEG outputs. The manifest maps stable source identities to output URLs and statuses.

Part 3: automate the same workflow in Python

Install Requests, put your real key in an environment variable, and keep the deliberately non-working placeholder in source control.

python -m pip install requests

# macOS or Linux
export PIXELAPI_API_KEY="pxapi_your_key_here"

# PowerShell
$env:PIXELAPI_API_KEY = "pxapi_your_key_here"

Save this as wedding_batch.py. Put an approved reference plus your selected JPEGs inside wedding_photos/.

import os
import time
import zipfile
from pathlib import Path

import requests

BASE = "https://api.pixelapi.dev"
API_KEY = os.getenv("PIXELAPI_API_KEY", "pxapi_demo_not_a_real_key")
PHOTOS = Path("wedding_photos")
REFERENCE = PHOTOS / "approved_reference.jpg"
OUTPUT = Path("wedding_delivery")

session = requests.Session()
session.headers.update({"Authorization": f"Bearer {API_KEY}"})

def api(method, path, **kwargs):
    url = path if path.startswith("http") else f"{BASE}{path}"
    response = session.request(method, url, timeout=120, **kwargs)
    response.raise_for_status()
    return response

# Reuse this key if your create request must be retried.
project = api("POST", "/v1/wedding-projects", json={
    "name": "Ananya and Rohan - final gallery",
    "preset": "reference_match",
    "idempotency_key": "ananya-rohan-final-v1",
    "preserve_resolution": True,
    "jpeg_quality": 94,
}).json()
project_id = project["project_id"]

with REFERENCE.open("rb") as handle:
    api("POST", f"/v1/wedding-projects/{project_id}/assets",
        files={"file": (REFERENCE.name, handle, "image/jpeg")},
        data={"client_asset_id": "approved-reference",
              "sort_order": 0, "role": "reference"})

sources = sorted(p for p in PHOTOS.glob("*.jpg") if p != REFERENCE)
for index, photo in enumerate(sources):
    with photo.open("rb") as handle:
        api("POST", f"/v1/wedding-projects/{project_id}/assets",
            files={"file": (photo.name, handle, "image/jpeg")},
            data={"client_asset_id": f"source-{index:05d}",
                  "sort_order": index, "role": "source"})
    print(f"uploaded {index + 1}/{len(sources)}: {photo.name}")

api("POST", f"/v1/wedding-projects/{project_id}/start", json={})

while True:
    project = api("GET", f"/v1/wedding-projects/{project_id}").json()
    counts = project["counts"]
    print(project["progress_percent"], counts)
    if project["status"] in {
        "completed", "completed_with_errors", "failed", "cancelled"
    }:
        break
    time.sleep(2)

if project["status"] not in {"completed", "completed_with_errors"}:
    raise SystemExit(f"Project ended as {project['status']}")

OUTPUT.mkdir(exist_ok=True)
zip_path = OUTPUT / "wedding-delivery.zip"
zip_path.write_bytes(api("GET", project["delivery_url"]).content)
with zipfile.ZipFile(zip_path) as archive:
    archive.extractall(OUTPUT / "finished")

manifest = api(
    "GET", f"/v1/wedding-projects/{project_id}/manifest"
).json()
print(f"finished {manifest['project']['counts']['completed']} photos")
Why stable IDs matter: if the connection breaks after photo 640, rerun the upload loop with the same client_asset_id values. Existing assets return successfully instead of being stored twice.

Resume, cancel, and retry

NeedRequestBehavior
Find recent projectsGET /v1/wedding-projects?limit=20Returns only projects owned by the current account.
Resume stateGET /v1/wedding-projects/{id}Returns progress, counts, and delivery URLs.
List failed filesGET /v1/wedding-projects/{id}/assets?status=failedPaginate up to 500 assets per page.
Retry all failuresPOST /v1/wedding-projects/{id}/retry with {}Queues only failed source assets after a run finishes.
Retry selected failuresPOST /v1/wedding-projects/{id}/retrySend {"asset_ids":["uuid"]}.
CancelPOST /v1/wedding-projects/{id}/cancelA draft cancels immediately; an active job stops safely between assets.

Common responses

For the complete request and response fields, use the Wedding Projects API reference.