Three on-ramps. One credit wallet.
Ounie accounts hit the REST endpoints with a bearer key. AI assistants — including the Ounie AI Team — connect over MCP and get a native tool surface. One-off scripts can skip the account entirely and pay per shot in USDC via x402. All three drive the same compositing engine, and account calls draw your shared Ounie credits (1 credit = 1¢) — refused when short, never overdrawn.
A shot is the unit of work: one product photo, one scene (and/or custom direction), one composited image. Shots are waited on in-request for the common fast path (usually 15–60 seconds, up to 2 minutes) and return a finished URL; if the wait runs out the shot stays running and you poll GET /api/generations/:id until it settles.
Every shot keeps the product's exact shape, label, and colors identical to the reference photo — only the scene changes. A submit can queue up to 8 scenes on the same reference; each is its own shot and its own credit charge.
Base URL https://productstudio.ounie.com · JSON everywhere · custom prompts up to 1,200 characters · one product photo per shot · max 5 shots in flight per account.
The dashboard's own auth. Every REST endpoint accepts it — the docs below work from your browser session too.
pst_live_…Minted at Dashboard → API keys (shown once, up to 5 active). Send it as Authorization: Bearer pst_live_… or, where headers can't be set, as ?api_key=pst_live_….
ounie_live_…One key across the whole Ounie app fleet. Enable "Use across Ounie apps" on the key at ounie.com → Settings → API keys and it works here as a Bearer too — the AI Team connects this way by default.
Submitting a shot reservesthe quality tier's full credit price from the caller's shared Ounie wallet (Standard 12 cr, Pro 40 cr). Delivery settles the reservation; a failed, timed-out, or aborted job refunds it automatically and in full — exactly once, however many pollers race.
If the wallet can't cover the reserve, the call is refused up front with 402 insufficient_credits and a buy_credits_url (https://ounie.com/dashboard/settings). Nothing is charged, nothing goes negative — an agent on your key can never overdraw you.
Free by design: GET reads, the gallery, downloads, share pages, and reference-photo uploads.
/api/media/reference— Upload a reference photoMultipart "file" or JSON {"url": "https://…"} — up to 10 MB, sniffed server-side, stored private. Returns a reference_image_key for the next call. Free.
/api/generations— Create a shot{
"model_id": "pro", // "standard" | "pro" (default "pro")
"scene": "minimalist-marble", // from GET /api/pricing, or omit with custom_prompt
"custom_prompt": "…", // optional, up to 1,200 chars
"aspect_ratio": "1:1", // optional, model's options
"reference_image_key": "…" // required, from /api/media/reference
}Reserves the quality tier's price (Standard = 12 credits, Pro = 40 credits). Waits in-request up to 2 minutes and usually returns 200 with a finished URL; a slow render returns 202 with status "running" — poll the detail route.
/api/generations— List shotsQuery params: status=queued|running|succeeded|failed|aborted, limit, offset. Free.
/api/generations/:id— Poll one shotLazily advances a still-rendering job — each call can settle or refund it. This is how agents wait on a shot. Free.
/api/generations/:id— Abort or deleteA queued/running shot is aborted with a full refund; a finished one is deleted along with its stored output. Free.
/api/generations/:id/share— Toggle the share page{ "public": true }Succeeded shots only. Returns share_url (/g/:id). Default is private — a shot goes public only by explicit owner action. Free.
/api/pricing— The price listPublic — quality tiers, the scene catalog, and the x402 price, all live. No auth. Also backs list_scenes / get_pricing over MCP.
Endpoint https://productstudio.ounie.com/api/mcp (legacy SSE: /api/sse) Header Authorization: Bearer pst_live_… (or ounie_live_…) # Clients that can't set headers append the key to the URL — # this is how the Ounie AI Team connects: https://productstudio.ounie.com/api/mcp?api_key=pst_live_…
| Tool | What it does | Cost |
|---|---|---|
| create_product_shot | Composite a product photo into a studio scene. Give a public product_image_url plus a scene (from list_scenes) and/or custom_prompt. Waits for the render (usually 15–60s) and returns a permanent public image URL. | 12 or 40 credits by quality |
| get_shot | Poll a shot; advances a still-rendering job and settles or refunds when it finishes. | free |
| list_shots | List your shots, filterable by status. | free |
| list_scenes | The scene catalog (id, name, description). No auth required. | public |
| get_credit_balance | Spendable Ounie credits in the shared wallet. | free |
| get_pricing | Credit tiers + the x402 endpoint. No auth required. | public |
| whoami | The authenticated key's owner and key id. | free |
create_product_shot takes a public product_image_url— the tool imports it server-side, so there's no separate upload step over MCP the way REST needs one.
# 1 — ask; the server answers with payment terms
curl -X POST https://productstudio.ounie.com/api/x402/generate \
-H "Content-Type: application/json" \
-d '{"product_image_url":"https://…/mug.png","scene":"sunny-beach"}'
# → 402 { "x402Version": 1, "accepts": [{ "maxAmountRequired", "payTo", "asset", … }] }
# 2 — sign the terms with your wallet and retry
curl -X POST https://productstudio.ounie.com/api/x402/generate \
-H "Content-Type: application/json" \
-H "X-Payment: <signed payload>" \
-d '{"product_image_url":"https://…/mug.png","scene":"sunny-beach"}'
# → 200 { "ok": true, "url": "https://…/outputs/….png",
# "payment": { "tx_hash", "payer", "paid_atomic_usdc" } }| Endpoint | Quality | Price |
|---|---|---|
| POST /api/x402/generate | always Pro — one static price | 500000 atomic USDC (≈ $0.50) |
Plainly: x402 needs a product_image_url the server can fetch (public https), a scene and/or custom_prompt, and settles synchronously — no polling, no gallery row, no Ounie account.
{ "error": "code", … }. A charged-then-failed job always refunds.| Code | Status | Meaning |
|---|---|---|
| unauthorized | 401 | Missing or invalid credential — send a session cookie, a pst_live_ Bearer, or ?api_key=. |
| insufficient_credits | 402 | The wallet can't cover the reserve. The body includes required, balance, and buy_credits_url. Nothing was charged. |
| invalid_payload / prompt_too_long | 400 | The request body failed validation (custom_prompt is up to 1,200 chars; aspect_ratio must be one of the model's options). |
| unknown_model / unknown_scene | 400 | model_id or scene isn't in the catalog. |
| reference_required / scene_or_prompt_required | 400 | A shot needs a reference_image_key and at least one of scene or custom_prompt. |
| prompt_blocked | 400 | The prompt failed content screening. Not charged. |
| too_many_running | 429 | You already have 5 shots in flight — wait for one to finish or abort one. |
| not_found | 404 | No shot with that id belongs to this account. |
| generation_unavailable | 503 | Generation isn't configured on this deployment. |
| generation_failed | 502 | The provider refused or failed the job. Reserved credits were refunded. |
{ "generation": … }.{
"id": "9f2c…", // uuid
"model_id": "pro", // standard | pro
"tier": "pro",
"status": "succeeded", // queued | running | succeeded | failed | aborted
"scene": "minimalist-marble",
"custom_prompt": null,
"aspect_ratio": "1:1",
"outputs": ["https://…/outputs/….png"],
"is_public": false, // share page off by default
"error": null,
"source": "api", // web | api | mcp
"credits_reserved": 40,
"credits_spent": 40, // 0 on failure — the reserve was refunded
"created_at": "2026-07-14T…",
"completed_at": "2026-07-14T…"
}