Node.js · AI mannequin generation API
AI mannequin generation API in Node.js
In one sentence
Set PHOTTA_API_KEY in env, write a tiny fetch helper for https://api.photta.app/api/v1 with a Bearer key, POST to /api/v1/mannequins/generate, then poll /api/v1/mannequins/generate/{id} every 3 seconds until status is completed.
- Typical time: 30-60s
- Credits: 4 credits
- 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/mannequins/generate \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"category": "Female",
"ethnicity": "east_asian_korean",
"age": "late_20s",
"skin_tone": "light_medium",
"expression": "neutral",
"hair_style": "long_straight",
"hair_color": "black",
"body_type": "slim",
"height": "average"
}')
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/mannequins/generate/$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/mannequins/generate", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"category": "Female",
"ethnicity": "east_asian_korean",
"age": "late_20s",
"skin_tone": "light_medium",
"expression": "neutral",
"hair_style": "long_straight",
"hair_color": "black",
"body_type": "slim",
"height": "average"
}),
});
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/mannequins/generate/${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/mannequins/generate",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"category": "Female",
"ethnicity": "east_asian_korean",
"age": "late_20s",
"skin_tone": "light_medium",
"expression": "neutral",
"hair_style": "long_straight",
"hair_color": "black",
"body_type": "slim",
"height": "average"
},
)
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/mannequins/generate/{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/mannequins/generate
- Authentication
- Bearer API key
- Processing
- Asynchronous — poll the status endpoint
- Typical time
- 30-60s
- Credits
- 4 credits
- Input limits
- Up to 10 MB per image; JPEG, PNG, WebP
- Idempotent
- No
How it works
Five steps, about five minutes from signup to first image.
Step 1
Sign up and generate a key
Create an account at ai.photta.app. Open the Developers section of the dashboard, click Generate API key, and grab the live key. It starts with `photta_live_` followed by 32 hex characters.
Step 2
Store the key in env, not source
Add `PHOTTA_API_KEY=...` to `.env` (or your platform's secret store) and load it via `process.env`. Never commit the key; never import it into a file marked `'use client'` — the bundler will pull it into the browser.
node --version # 18 or later ships fetch echo "PHOTTA_API_KEY=photta_live_xxxxx" >> .envStep 3
Write a tiny fetch helper
One 20-line wrapper around `fetch()` handles the base URL, Authorization header, JSON body and error normalisation. This is the only abstraction you need until an official SDK ships.
// photta.js — one helper for every endpoint const BASE = "https://api.photta.app/api/v1"; export async function photta(path, init = {}) { const res = await fetch(BASE + path, { ...init, headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json", ...(init.headers ?? {}) } }); const body = await res.json(); if (!res.ok) throw Object.assign(new Error(body.error?.message ?? res.statusText), { status: res.status, retryAfter: res.headers.get("Retry-After"), body }); return body; }Step 4
Submit a try-on and poll
POST to `/api/v1/mannequins/generate` with the documented example body — the code below carries every field. The API returns a generation ID immediately. Poll `/api/v1/mannequins/generate/{id}` every 3 seconds until `status === 'completed'`.
const res = await fetch("https://api.photta.app/api/v1/mannequins/generate", { method: "POST", headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" }, body: JSON.stringify({ "category": "Female", "ethnicity": "east_asian_korean", "age": "late_20s", "skin_tone": "light_medium", "expression": "neutral", "hair_style": "long_straight", "hair_color": "black", "body_type": "slim", "height": "average" }), }); 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/mannequins/generate/${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"); }Step 5
Persist the result
The completed payload includes `output_url` and `thumbnail_url`. Fetch the bytes once and store them in your own object storage — the URLs are stable but your product shouldn't depend on Photta's CDN for 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 {
await photta("/mannequins/generate", { method: "POST", body: JSON.stringify({
"category": "Female",
"ethnicity": "east_asian_korean",
"age": "late_20s",
"skin_tone": "light_medium",
"expression": "neutral",
"hair_style": "long_straight",
"hair_color": "black",
"body_type": "slim",
"height": "average"
}) });
} catch (err) {
if (err.status === 402) console.error("insufficient_credits_error: top up before retrying");
else if (err.status === 429) await new Promise((r) => setTimeout(r, Number(err.retryAfter ?? 30) * 1000));
else throw err;
}Limits
- 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 a helper beats a client library — for now
- Node 18+ ships native fetch — no runtime dependencies
- The same helper works inside Next.js route handlers, server actions and any worker
- Keep the API key in env; never import the helper from a 'use client' module or the bundler will leak it
- Works with any queue or cron: BullMQ, Inngest, Trigger.dev, Vercel Cron, Cloudflare Queues
What it does not do
- No official @photta/node package yet — REST is the only path today
- 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 Node.js SDK?
Not yet. The documented path is raw REST via Node's native fetch — the helper on this page is the whole integration surface, and it works the same for every capability.
What Node.js versions are supported?
Anything that supports native `fetch` — which means Node 18 and later. On Node 16 you either polyfill with `node-fetch` or upgrade. The rest of the code is plain ECMAScript 2020 (async/await, template literals, dynamic imports) so no other runtime requirements.
How do I authenticate from Node?
Send `Authorization: Bearer photta_live_xxx` on every request. Put the key in an environment variable and load it via `process.env`; never import it from a file that runs in the browser.
How do I handle the generation window?
The generation endpoint is fully asynchronous. POST returns a generation ID immediately with `status: 'processing'`. Poll `GET /api/v1/mannequins/generate/{id}` every 3–5 seconds until `status` flips to `completed` or `failed`, and cap the attempts so a stuck job cannot hang a request.
Can I call the API inside Next.js?
Yes — from a route handler (`app/**/route.ts`), a server action (`'use server'`), or middleware. Keep the API key on the server; never import the helper from a `'use client'` file. For long-running jobs that exceed Vercel's function timeout, defer the polling to a queue (Inngest, Trigger.dev, BullMQ) and return the generation ID to the browser so it can poll your own status endpoint.
How does the API signal errors?
Non-2xx responses carry a JSON body with an `error` object: `type`, `code`, `message` and a request ID for support. Common cases: 400 invalid_request_error with `param` pointing at the bad field; 402 insufficient_credits with `required` and `available` counts; 429 rate_limit_exceeded with `retry_after` in seconds and a matching Retry-After header. 5xx errors are safe to retry with exponential backoff.
Related pages
Python
AI mannequin generation API in Python
Call Photta's aI mannequin generation API in Python with the requests library. Thirty-line wrapper, async-friendly via httpx, works in Django, FastAPI and Celery.
ReadcURL
AI mannequin generation API with cURL
Call Photta's aI mannequin generation API directly from the shell with cURL. Submit, poll, download — every snippet pairs with jq for scripted pipelines.
ReadCapabilities
AI mannequin generation API for your own models
Generate photorealistic AI models from attributes and reuse them across try-on shoots. Apparel and jewelry builders, 4 credits per mannequin.
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.
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.
- 30-60s
- 4 credits
- async, polling
- 600 requests/min
- Bearer key