Python · Virtual Try-On API
Virtual Try-On API in Python
No pip install photta is needed — the Photta docs officially recommend the requests library. A thirty-line wrapper plugs into Django, FastAPI, Celery workers and Lambda handlers without further ceremony.
In one sentence
Install `requests`, load `PHOTTA_API_KEY` from env, write a `photta_request()` wrapper that sends `Authorization: Bearer photta_live_xxx` and raises a typed `PhottaError` on non-2xx responses, then POST to `/tryon/apparel` and poll `/tryon/apparel/:id` every 3 seconds until `data.status == 'completed'` — typically within 1.5 to 4 minutes.
Updated · 2026-04-19
Your first request
import time
from photta import photta_request, PhottaError
# 1. Submit the job
created = photta_request("POST", "/tryon/apparel", json={
"product_type": "dress",
"product_images": ["https://example.com/dress.jpg"],
"mannequin_id": "mnq_athena_ts",
"pose_id": "pose_standing_front",
"resolution": "2K",
"aspect_ratio": "3:4",
})
generation_id = created["data"]["id"]
# 2. Poll every 3 seconds. Upper bound: 120 attempts ≈ 6 minutes,
# comfortably above the documented 1.5–4 minute window.
for _ in range(120):
result = photta_request("GET", f"/tryon/apparel/{generation_id}")
status = result["data"]["status"]
if status == "completed":
print("Result:", result["data"]["output_url"])
break
if status == "failed":
raise RuntimeError(result["data"]["error_message"])
time.sleep(3)What to expect
Typical completion
1–3min
2K / 4K credits
4 / 6
Styles
2
Batch-ready
yes
How it works
Virtual Try-On API in Python
Five steps, zero Photta-specific dependencies — requests covers everything.
- 01
Step 1
Sign up and generate a key
Same path as every other language: ai.photta.app → Developers → Generate API key. Keys start with `photta_live_` and work across every Photta capability.
- 02
Step 2
Load the key from env
Set `PHOTTA_API_KEY` in the environment and read it via `os.environ`. In FastAPI or Django, load it once at boot into a settings module. Don't embed the key in source code or public Git history.
- 03
Step 3
Write a requests wrapper
A thirty-line `photta_request()` function handles the base URL, the Authorization header, JSON serialisation and error translation. That's the only abstraction you need until the Python SDK lands.
- 04
Step 4
Submit and poll
POST to `/tryon/apparel` to get a generation ID, then poll `/tryon/apparel/:id` on a 3-second interval. In Celery or RQ, break the poll loop across tasks so a 4-minute job doesn't tie up a worker.
- 05
Step 5
Persist the result
The completed payload includes `output_url` and `thumbnail_url`. Download the bytes into your own object storage — S3, GCS, Cloudflare R2 — so your product isn't tied to Photta's CDN for long-term rendering.
Code, end to end
Copy, paste, done.
Four snippets — install prerequisites, wrap the REST call, submit + poll, then handle the errors that actually happen in production.
# Photta doesn't ship an official Python SDK yet — requests is the
# recommended path in the docs. Standard library urllib works too if
# you need zero dependencies.
pip install requests
# Store your API key in env (dotenv optional but convenient)
echo "PHOTTA_API_KEY=photta_live_xxxxx" >> .env# photta.py
import os
import requests
PHOTTA_BASE_URL = "https://ai.photta.app/api/v1"
class PhottaError(Exception):
def __init__(self, status: int, code: str, message: str, retry_after: int | None = None):
super().__init__(message)
self.status = status
self.code = code
self.retry_after = retry_after
def photta_request(method: str, path: str, **kwargs):
headers = {
"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}",
"Content-Type": "application/json",
**kwargs.pop("headers", {}),
}
response = requests.request(method, f"{PHOTTA_BASE_URL}{path}", headers=headers, **kwargs)
body = response.json()
if not response.ok:
err = body.get("error", {})
raise PhottaError(
status=response.status_code,
code=err.get("code", "unknown"),
message=err.get("message", response.reason),
retry_after=err.get("retry_after"),
)
return bodyimport time
from photta import photta_request, PhottaError
# 1. Submit the job
created = photta_request("POST", "/tryon/apparel", json={
"product_type": "dress",
"product_images": ["https://example.com/dress.jpg"],
"mannequin_id": "mnq_athena_ts",
"pose_id": "pose_standing_front",
"resolution": "2K",
"aspect_ratio": "3:4",
})
generation_id = created["data"]["id"]
# 2. Poll every 3 seconds. Upper bound: 120 attempts ≈ 6 minutes,
# comfortably above the documented 1.5–4 minute window.
for _ in range(120):
result = photta_request("GET", f"/tryon/apparel/{generation_id}")
status = result["data"]["status"]
if status == "completed":
print("Result:", result["data"]["output_url"])
break
if status == "failed":
raise RuntimeError(result["data"]["error_message"])
time.sleep(3)from photta import photta_request, PhottaError
import time
try:
photta_request("POST", "/tryon/apparel", json={ ... })
except PhottaError as err:
if err.status == 402:
# Out of credits — err.code == "insufficient_credits"
raise
elif err.status == 429:
# Rate-limited. Honour the Retry-After header.
time.sleep(err.retry_after or 30)
# then retry…
elif err.status >= 500:
# Server-side — retry with exponential backoff.
raise
else:
raiseWhy this shape
Why requests is the right pick today
- Zero Photta-specific dependencies — `requests` is already in most Python projects
- Works anywhere Python runs: Django, FastAPI, Flask, Celery workers, Lambda, scripts
- The client wrapper is ~30 lines — paste it into `photta.py` and move on
- Pairs naturally with asyncio via `httpx` if you need parallel submission
What it doesn't do
Honest caveats
- No official @photta/python SDK yet — REST + requests is the documented pattern
- No async fetch helper shipped; swap `requests` for `httpx` when you need concurrency
- Bearer token auth only — no OAuth client credentials yet
Questions other developers ask
Questions other developers ask
Is there an official Photta Python SDK?+
Not yet. The Photta roadmap prioritises the Python SDK once ten paying API customers are live. Until then, the supported integration path is the requests library (synchronous) or httpx (async) — both are documented in the official docs and both work with the thirty-line wrapper on this page.
What Python versions are supported?+
Python 3.9 and later. The wrapper uses type hints, f-strings and `str | None` syntax, which is fine on 3.10+. On 3.9 swap `str | None` for `Optional[str]`. requests itself officially supports 3.8+, but 3.9+ is the practical minimum for modern Photta projects.
Should I use requests or httpx?+
requests if you're making a handful of sequential calls — it's simpler and already in most codebases. httpx if you need async concurrency (parallel submits, bulk backfills) or HTTP/2. The wrapper is tiny either way; swap `requests.request` for `httpx.Client().request` and the rest of the code stays identical.
Can I call it from Django?+
Yes. Put the wrapper in `services/photta.py`, import it from a view or a Celery task. Don't call the API inside a synchronous request handler if the job is long — submit from the view, return the generation ID, and poll from a background task or a Channels consumer.
How do I poll from a Celery worker?+
Submit the job from one task, then schedule a follow-up task (via `apply_async(countdown=3)`) that polls once and re-schedules itself until the job completes or a max-retries threshold hits. This keeps workers free and avoids long-running tasks that upset autoscalers.
How does the API signal errors?+
Non-2xx responses carry a JSON body with an `error` object: `type`, `code`, `message`, plus `param` on 400s and `retry_after` on 429s. The wrapper on this page raises a `PhottaError` carrying all of these so your callers can branch on status code without parsing the body themselves.
Python · Virtual Try-On API
Create an account and get an API key
Install `requests`, load `PHOTTA_API_KEY` from env, write a `photta_request()` wrapper that sends `Authorization: Bearer photta_live_xxx` and raises a typed `PhottaError` on non-2xx responses, then POST to `/tryon/apparel` and poll `/tryon/apparel/:id` every 3 seconds until `data.status == 'completed'` — typically within 1.5 to 4 minutes.