cURL · Ghost mannequin API
Ghost mannequin API with cURL
In one sentence
Export PHOTTA_API_KEY, POST to https://api.photta.app/api/v1/api/v1/ghost-mannequin with the documented JSON body, capture the generation ID, then loop a GET on /api/v1/ghost-mannequin/{id} every 3 seconds until data.status is completed. Pair with jq to extract the output URL.
- Typical time: 1.5–4 min
- Credits: 4 credits at 2K · 5 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/ghost-mannequin \
-H "Authorization: Bearer $PHOTTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
}')
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/ghost-mannequin/$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/ghost-mannequin", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
}),
});
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/ghost-mannequin/${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/ghost-mannequin",
headers={"Authorization": f"Bearer {os.environ['PHOTTA_API_KEY']}"},
json={
"product_image": "https://cdn.yourstore.com/products/jacket-front.jpg",
"back_image": "https://cdn.yourstore.com/products/jacket-back.jpg",
"image_side": "front",
"angle": "side",
"style": "3d",
"background_color": "white",
"resolution": "2K",
"aspect_ratio": "3:4",
"user_instruction": "closed front, cream interior lining",
"brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67"
},
)
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/ghost-mannequin/{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/ghost-mannequin
- Authentication
- Bearer API key
- Processing
- Asynchronous — poll the status endpoint
- Typical time
- 1.5–4 min
- Credits
- 4 credits at 2K · 5 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 shell steps, zero dependencies beyond curl and jq.
Step 1
Sign up and generate a key
Head to ai.photta.app, open the Developers tab, click Generate API key. Live keys start with `photta_live_`.
Step 2
Export the key
Put `export PHOTTA_API_KEY="photta_live_xxx"` in your shell profile or a `.envrc` file so every subsequent curl call picks it up automatically.
curl --version # macOS, Linux, WSL and Windows 10+ ship it export PHOTTA_API_KEY="photta_live_xxxxx"Step 3
Pin the base URL
`export PHOTTA_BASE_URL="https://api.photta.app/api/v1"` keeps the curl commands short.
export PHOTTA_BASE_URL="https://api.photta.app/api/v1" export AUTH_HEADER="Authorization: Bearer $PHOTTA_API_KEY"Step 4
Submit and poll
POST to `$PHOTTA_BASE_URL/api/v1/ghost-mannequin` to get an ID, then loop `curl $PHOTTA_BASE_URL/api/v1/ghost-mannequin/{id}` every 3 seconds. Pipe the response to jq to extract `.data.status` cleanly.
RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/ghost-mannequin \ -H "Authorization: Bearer $PHOTTA_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "product_image": "https://cdn.yourstore.com/products/jacket-front.jpg", "back_image": "https://cdn.yourstore.com/products/jacket-back.jpg", "image_side": "front", "angle": "side", "style": "3d", "background_color": "white", "resolution": "2K", "aspect_ratio": "3:4", "user_instruction": "closed front, cream interior lining", "brand_label_id": "7cc9f988-068b-44cd-8004-22f486b10e67" }') 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/ghost-mannequin/$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'Step 5
Persist the result
When the job completes, pull `.data.output_url` out with jq and `curl -o` the bytes into local storage — ready for your next pipeline step.
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.
case "$STATUS_CODE" in
402) echo "insufficient_credits_error — top up before retrying" ;;
429) echo "rate_limit_error — wait the Retry-After seconds (30 by default)" ;;
4??) echo "$BODY" | jq '.error.type, .error.message' ;;
5??) echo "api_error — retry with backoff" ;;
esacLimits
- 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 cURL is still the fastest way to verify an API
- Zero install — cURL ships with every major OS
- Pairs with jq for scripted pipelines (CI, cron, shell hooks)
- The fastest way to smoke-test an endpoint before wiring it into code
- Every command on this page pastes into any POSIX shell
What it does not do
- The polling loop blocks the terminal — move to Node or Python for parallel workers
- Polling is the only completion signal; the API sends no webhooks
- Windows Command Prompt users should run WSL or PowerShell's curl.exe
Questions developers ask
Is there a Photta CLI tool?
Not yet — but cURL plus a fifteen-line bash wrapper gets you most of the way. The docs prioritise cURL-first examples because every language can run a subprocess, and the shell is the common denominator in CI pipelines.
Do I need jq to use the API?
Not strictly — but scripted pipelines are much easier with it. jq lets you pull `.data.id`, `.data.status` and `.data.output_url` out of the JSON response without parsing it by hand. Install with `brew install jq` on macOS or `apt install jq` on Debian/Ubuntu.
How do I authenticate with curl?
Add `-H "Authorization: Bearer $PHOTTA_API_KEY"` to every request. Keep the key in an env var, never inline it in a script you commit. `curl --oauth2-bearer` also works but the explicit header form is clearer in docs and logs.
Can I use curl-based scripts in production?
For one-off batch backfills and cron jobs, yes — thousands of teams ship catalog backfills this way. For a long-lived application, a real language is usually better because you get proper error handling, retries, and observability. The curl examples on this page stay accurate even after you port to Node or Python; the request shape is the same.
How do I poll from a bash loop?
Use `for i in $(seq 1 120); do … ; sleep 3; done` with the status check in the loop body. Break out when `.data.status` is `completed` or `failed`. The code sample on this page shows the exact pattern, including a hard upper bound so a stuck job can't hang the script forever.
How do I read error responses in curl?
Use `curl -w "\n%{http_code}"` to capture the HTTP status code alongside the JSON body. Branch on the status: 402 for insufficient credits, 429 for rate limits (honour the Retry-After value), 5xx for transient server errors. The error body always has an `error.code` and `error.message` field.
Related pages
Node.js
Ghost mannequin API in Node.js
Call Photta's ghost mannequin API from a Node.js backend with native fetch. No SDK required: store your key, submit the job, poll for the result.
ReadPython
Ghost mannequin API in Python
Call Photta's ghost mannequin API in Python with the requests library. Thirty-line wrapper, async-friendly via httpx, works in Django, FastAPI and Celery.
ReadCapabilities
Ghost mannequin API for hollow-man product photos
AI ghost mannequin endpoint. Remove the mannequin from a garment photo and preserve interior detail. 3D or flat-lay, batch processing, 4 credits at 2K.
ReadBuild
Automate ghost mannequin extraction for your catalog
Nightly catalog automation recipe: pull yesterday's product updates, batch-submit to Photta's ghost-mannequin API, poll the parent batch, persist into your database.
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
- 4 credits at 2K · 5 credits at 4K
- async, polling
- 600 requests/min
- Bearer key