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 — Create or resume a project.
Name the gallery and click Create project, or choose a saved project. Add JPEG, PNG or WebP photos. Reselect the same files after an interrupted upload; confirmed uploads are skipped.
Step 3 — Analyze and review.
Click Analyze gallery for focus, exposure and duplicate suggestions. Use Keep and star ratings to choose your set. Keep suggested duplicates selects one recommendation per group; check meaningful moments before accepting a suggestion.
Step 4 — Preview your look.
Choose True Color, Natural Warm, Cinematic Soft or Reference Match. Adjust exposure, warmth, contrast, saturation and strength. Reference Match needs a reference upload. Click a photo or Preview selected photo for the free before/after slider. Original faces are preserved; eye checks are advisory and do not generate open eyes.
Step 5 — Finish selected photos.
The button shows the exact selected count and credit cost. Click Finish selected when satisfied. Processing continues if you leave the page. Selection and the recipe are locked for this run.
Step 6 — Compare and deliver.
Reopen any finished photo to compare it with its saved original. Download the finished ZIP. Retry failed photos only; failed and unprocessed photos are refunded.

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",
    "workflow_version": 2,
    "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.