API

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.

Overview
Product-photo compositing across 14 scenes and 2 quality tiers, as an API.

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.

Authentication
Three credentials, one rule: the caller owns everything it touches.
Session cookie

The dashboard's own auth. Every REST endpoint accepts it — the docs below work from your browser session too.

App key · 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_….

Master key · 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.

Credits & refusal semantics
Reserve → settle → refund. Refused, never overdrawn.

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.

REST endpoints
The same routes the dashboard uses. All accept cookie or Bearer auth.
POST/api/media/referenceUpload a reference photo

Multipart "file" or JSON {"url": "https://…"} — up to 10 MB, sniffed server-side, stored private. Returns a reference_image_key for the next call. Free.

POST/api/generationsCreate 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.

GET/api/generationsList shots

Query params: status=queued|running|succeeded|failed|aborted, limit, offset. Free.

GET/api/generations/:idPoll one shot

Lazily advances a still-rendering job — each call can settle or refund it. This is how agents wait on a shot. Free.

DELETE/api/generations/:idAbort or delete

A queued/running shot is aborted with a full refund; a finished one is deleted along with its stored output. Free.

POST/api/generations/:id/shareToggle 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.

GET/api/pricingThe price list

Public — quality tiers, the scene catalog, and the x402 price, all live. No auth. Also backs list_scenes / get_pricing over MCP.

MCP server
Native tools for Claude, Cursor, ChatGPT, the AI SDK — and the Ounie AI Team.
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_…
ToolWhat it doesCost
create_product_shotComposite 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_shotPoll a shot; advances a still-rendering job and settles or refunds when it finishes.free
list_shotsList your shots, filterable by status.free
list_scenesThe scene catalog (id, name, description). No auth required.public
get_credit_balanceSpendable Ounie credits in the shared wallet.free
get_pricingCredit tiers + the x402 endpoint. No auth required.public
whoamiThe 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.

x402 · keyless pay-per-call
No account, no key, no credits — the USDC is the payment. One Pro shot per call.
# 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" } }
EndpointQualityPrice
POST /api/x402/generatealways Pro — one static price500000 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.

Errors
Errors are JSON: { "error": "code", … }. A charged-then-failed job always refunds.
CodeStatusMeaning
unauthorized401Missing or invalid credential — send a session cookie, a pst_live_ Bearer, or ?api_key=.
insufficient_credits402The wallet can't cover the reserve. The body includes required, balance, and buy_credits_url. Nothing was charged.
invalid_payload / prompt_too_long400The 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_scene400model_id or scene isn't in the catalog.
reference_required / scene_or_prompt_required400A shot needs a reference_image_key and at least one of scene or custom_prompt.
prompt_blocked400The prompt failed content screening. Not charged.
too_many_running429You already have 5 shots in flight — wait for one to finish or abort one.
not_found404No shot with that id belongs to this account.
generation_unavailable503Generation isn't configured on this deployment.
generation_failed502The provider refused or failed the job. Reserved credits were refunded.
The shot object
Returned by every generation route as { "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…"
}