Python · Virtual try-on API
Virtual try-on API in Python
In one sentence
Install requests, load PHOTTA_API_KEY from env, write a photta() wrapper that sends the Bearer key and raises a typed PhottaError on non-2xx responses, then POST to /api/v1/tryon/apparel and poll /api/v1/tryon/apparel/{id} every 3 seconds until status is completed.
- Typical time: 1.5–4 min
- Credits: 5 credits at 2K · 6 credits at 4K
- Processing: async, polling
- Limits: 600 requests/min
- Authentication: Bearer key
Every figure on this page comes from the Photta API docs · checked
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')
# $ID is data.id from the POST response. Poll every 3 seconds until status is "completed" or "failed".
for i in $(seq 1 120); do
RESULT=$(curl -s "https://api.photta.app/api/v1/tryon/apparel/$ID" -H "Authorization: Bearer $PHOTTA_API_KEY")
STATUS=$(echo "$RESULT" | jq -r '.data.status')
[ "$STATUS" = "completed" ] && break
[ "$STATUS" = "failed" ] && echo "generation failed" && exit 1
sleep 3
done
echo "$RESULT" | jq -r '.data.output_url'const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": true,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
}),
});
const { data } = await res.json(); // data.id, data.status
async function waitFor(id) {
for (let attempt = 0; attempt < 120; attempt++) {
const res = await fetch(`https://api.photta.app/api/v1/tryon/apparel/${id}`, { headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` } });
const { data } = await res.json();
if (data.status === "completed") return data.output_url;
if (data.status === "failed") throw new Error(data.error_message ?? "generation failed");
await new Promise((r) => setTimeout(r, 3000));
}
throw new Error("timed out after 6 minutes");
}import os, requests
res = requests.post(
"https://api.photta.app/api/v1/tryon/apparel",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": True,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
},
)
data = res.json()["data"] # data["id"], data["status"]
import time
def wait_for(job_id):
for _ in range(120):
res = requests.get(f"https://api.photta.app/api/v1/tryon/apparel/{job_id}", headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"})
data = res.json()["data"]
if data["status"] == "completed":
return data["output_url"]
if data["status"] == "failed":
raise RuntimeError(data.get("error_message", "generation failed"))
time.sleep(3)
raise TimeoutError("timed out after 6 minutes")At a glance
At a glance
- Endpoint
- POST /api/v1/tryon/apparel
- Authentication
- Bearer API key
- Processing
- Asynchronous — poll the status endpoint
- Typical time
- 1.5–4 min
- Credits
- 5 credits at 2K · 6 credits at 4K
- Aspect ratios
- 3:4 · 1:1 · 4:3 · 9:16 · 16:9
- Input limits
- Up to 10 MB per image; JPEG, PNG, WebP
- Idempotent
- No
How it works
Five steps, zero Photta-specific dependencies — requests covers everything.
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.
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.
python3 -m pip install requests export PHOTTA_API_KEY="photta_live_xxxxx"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.
# photta.py — one helper for every endpoint import os, requests BASE = "https://api.photta.app/api/v1" class PhottaError(Exception): def __init__(self, status, error, retry_after=None): super().__init__(error.get("message", "request failed")) self.status, self.type, self.retry_after = status, error.get("type"), retry_after def photta(method, path, **kwargs): res = requests.request(method, BASE + path, headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"}, timeout=30, **kwargs) body = res.json() if not res.ok: raise PhottaError(res.status_code, body.get("error", {}), res.headers.get("Retry-After")) return bodyStep 4
Submit and poll
POST to `/api/v1/tryon/apparel` to get a generation ID, then poll `/api/v1/tryon/apparel/{id}` on a 3-second interval. In Celery or RQ, break the poll loop across tasks so a multi-minute job does not tie up a worker.
import os, requests res = requests.post( "https://api.photta.app/api/v1/tryon/apparel", headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"}, json={ "product_type": "top", "product_images": [ "https://cdn.yourstore.com/products/shirt-front.jpg" ], "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg", "product_category": "t_shirt", "mannequin_id": "mnk_f3a9b1", "pose_id": "pose_c2d4e5", "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg", "lock_background": True, "resolution": "2K", "aspect_ratio": "3:4", "body_size": "M", "expression": "neutral", "user_instruction": "Keep the collar pattern crisp and centered." }, ) data = res.json()["data"] # data["id"], data["status"] import time def wait_for(job_id): for _ in range(120): res = requests.get(f"https://api.photta.app/api/v1/tryon/apparel/{job_id}", headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"}) data = res.json()["data"] if data["status"] == "completed": return data["output_url"] if data["status"] == "failed": raise RuntimeError(data.get("error_message", "generation failed")) time.sleep(3) raise TimeoutError("timed out after 6 minutes")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.
Errors
- 400
invalid_request_error - The request is malformed or contains invalid parameters.
- 401
authentication_error - The API key is missing, invalid, or expired.
- 402
insufficient_credits_error - Your account does not have enough credits.
- 404
not_found_error - The requested resource does not exist.
- 409
conflict_error - The request conflicts with current state — e.g. your concurrent-batch cap is reached.
- 429
rate_limit_error - Too many requests. Slow down and retry.Retry-After: 30
- 500
api_error - An internal server error occurred. Retry later.
try:
photta("POST", "/tryon/apparel", json={
"product_type": "top",
"product_images": [
"https://cdn.yourstore.com/products/shirt-front.jpg"
],
"product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
"product_category": "t_shirt",
"mannequin_id": "mnk_f3a9b1",
"pose_id": "pose_c2d4e5",
"background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
"lock_background": True,
"resolution": "2K",
"aspect_ratio": "3:4",
"body_size": "M",
"expression": "neutral",
"user_instruction": "Keep the collar pattern crisp and centered."
})
except PhottaError as e:
if e.status == 402:
print("insufficient_credits_error: top up before retrying")
elif e.status == 429:
time.sleep(int(e.retry_after or 30))
else:
raiseLimits
- 600 requests per minute per API key (Fixed window; the limit is the same on every plan)
- A 429 carries Retry-After: 30 seconds
- Up to 10 MB per image
- Formats: JPEG, PNG, WebP
- Concurrent batches per plan: Free 1 · Hobby 2 · Seller 3 · Brand 4 · Studio 6
- Polling only — the API sends no webhooks.
Why requests is the right pick today
- No Photta-specific dependency — requests is already in most Python projects
- Works anywhere Python runs: Django, FastAPI, Flask, Celery workers, Lambda, scripts
- The client wrapper is about thirty lines — paste it into photta.py and move on
- Pairs with asyncio via httpx when you need parallel submission
What it does not do
- No official Photta Python package yet — REST with requests is the documented pattern
- Polling is the only completion signal; the API sends no webhooks
- Bearer key only; there is no OAuth client-credentials flow
Questions developers ask
Is there an official Photta Python SDK?
Not yet. The supported integration path is the requests library (synchronous) or httpx (async) — both are a thin wrapper away, and the wrapper on this page works for every capability.
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.
Related pages
Node.js
Virtual try-on API in Node.js
Call Photta's virtual try-on API from a Node.js backend with native fetch. No SDK required: store your key, submit the job, poll for the result.
ReadcURL
Virtual try-on API with cURL
Call Photta's virtual try-on API directly from the shell with cURL. Submit, poll, download — every snippet pairs with jq for scripted pipelines.
ReadCapabilities
Virtual try-on API for apparel
REST API that places apparel on AI mannequins. Asynchronous, 1.5–4 minute generation, 5 credits at 2K, 6 at 4K. cURL, Node.js and Python examples.
ReadBuild
Build a virtual try-on feature for your app
End-to-end recipe for shipping a virtual try-on feature with the Photta API: upload, submit, poll from a worker, render the result. TypeScript + server-only patterns.
ReadBuild
Add virtual try-on to an iOS or Android app
How to add AI virtual try-on to a native iOS or Android app using the Photta API: never ship the key to the device, proxy through your backend, mirror job status.
ReadQuickstart
First Photta image in under five minutes.
Sign up, generate an API key, run your first cURL call, poll until it completes, persist the result. Five steps, documented end-to-end.
ReadReference
Photta API pricing
What each Photta API endpoint costs in credits at 2K and 4K, how charging and refunds work, and which endpoints are free. No per-image money price.
Read
Every figure on this page comes from the Photta API docs · checked
Try the API in five minutes
Generate a Photta API key, paste the cURL snippet above, and you'll have your first on-model image before the coffee finishes brewing.
- 1.5–4 min
- 5 credits at 2K · 6 credits at 4K
- async, polling
- 600 requests/min
- Bearer key