StyleMe for Your Own Inventory API
Connect your website, Shopify, catalog, or in-store inventory in real time, and dress every visiting customer in a complete, best-fit outfit pulled straight from it, turning window-shoppers into buyers. Want ZukMe's catalog instead? See StyleMe from ZukMe Marketplace.
Quickstart
Your catalog
PUT /inventory/products
StyleMe
style reading
1 creditInventory Try-On
rendered look
1 credit- 1Create an API key in the API Dashboard, then contact our team to get credits added — a new key starts with none.
- 2Push your catalog to
PUT /api/v1/inventory/products— a full replace, free. - 3Submit a photo to
POST /api/v1/stylemefor a style reading. - 4Submit that reading and a photo to
POST /api/v1/inventory/tryon, then poll until done.
User experience flow
A plain walkthrough of what your shopper experiences on your own site or app — no implementation detail, just the moments. You build the interface; each beat notes which call powers it.
(Merchant-side, before any shopper arrives) Your catalog is synced
Not something a shopper sees — your product list is pushed once, kept current whenever it changes. Everything matched below only ever comes from this list.
→ PUT /api/v1/inventory/products
Uploads or picks a photo
One clear photo of themselves — the only input this flow needs.
→ POST /api/v1/styleme
A short wait while their style is read
One photo in; a few seconds later, a personalized style profile out.
→ POST /api/v1/styleme (poll)
Sees their style read back to them (optional)
A style title, a palette, a few personality notes — however much of the reading you choose to surface before moving on.
Picks an occasion (optional)
"Client presentation," "Weekend brunch," whatever your UI offers — shapes which pieces from your catalog get shopped for next.
A short wait while an outfit is matched and rendered
Pieces from your own synced catalog — nowhere else — are matched to the reading and rendered onto their photo.
→ POST /api/v1/inventory/tryon
Sees themselves in the finished look
The rendered photo, plus every piece used broken out individually — name, price, your own product link. A category with nothing good in your catalog gets an AI-designed piece instead of being left empty, optionally paired with a real "shop similar" suggestion — see the Try-On response below.
Taps a piece to buy it
Every item links straight back to your own storefront — the whole point of syncing your own catalog.
Base URL
https://zukme.comAll endpoints below are relative to this base URL.
Authentication
Every request needs your API key as a bearer token — create and manage keys in the API Dashboard. The raw key is shown once; only its hash is stored.
Authorization: Bearer zk_live_<your key>Missing, malformed, or revoked key → 401:
{ "error": "Invalid or missing API key. Pass it as \"Authorization: Bearer zk_live_...\"." }Rate limits
30 requests per minute per key (submit and poll both count). Over the limit → 429:
{ "error": "Rate limit exceeded.", "retryAfterSeconds": 12 }Credits
Each successful submission costs 1 credit, deducted at submit and refunded automatically if generation fails. Polling is always free. Syncing your catalog is always free. A new API key starts with 0 credits — API and embed-widget usage spends from your account's bonus credit balance only, never your app subscription's monthly quota. Contact our team to get bonus credits added before building against this in production. Check your balance in the API Dashboard.
Errors
| Field | Type | Description |
|---|---|---|
400 | Bad Request | Missing or invalid fields in the request body. |
401 | Unauthorized | Missing, malformed, or revoked API key. |
402 | Payment Required | Credits exhausted (code CREDITS_EXHAUSTED), with your current looks/bonus/total balance in the body. |
404 | Not Found | jobId does not exist (expired, wrong id, or never submitted). |
429 | Too Many Requests | Rate limit exceeded for this key. See retryAfterSeconds. |
Example 402 body:
{
"error": "CREDITS_EXHAUSTED",
"code": "CREDITS_EXHAUSTED",
"looks": 0,
"bonus": 0,
"total": 0
}Step 1 — Sync your catalog
A full replace: whatever you send becomes your entire matchable catalog. Free — no credits spent syncing.
/api/v1/inventory/products| Field | Type | Description |
|---|---|---|
productsrequired | InventoryProduct[] | Your full product list. Replaces whatever was previously synced, up to 5000 items. |
The InventoryProduct object
| Field | Type | Description |
|---|---|---|
idrequired | string | Your own stable product id (e.g. SKU). |
namerequired | string | Product name. |
imageUrlrequired | string | Publicly fetchable product photo URL. |
price | string | Display price, e.g. "USD 84". |
description | string | Free text that helps matching quality. |
category | string | Free text, e.g. "top", "shoes" (helps matching quality, doesn't need to be an exact match). |
link | string | Product page URL on your own site. |
Response:
{ "ok": true, "count": 128 }/api/v1/inventory/productsReturns whatever is currently synced, to confirm a push landed:
{ "ok": true, "products": [ { "id": "sku-001", "name": "Tailored Wool Blazer", ... } ] }Step 2 — StyleMe reading
The personalized style reading Step 3 needs. Full field reference: StyleMe reading fields.
curl https://zukme.com/api/v1/styleme -X POST \
-H "Authorization: Bearer zk_live_..." -H "Content-Type: application/json" \
-d '{ "photoBase64": "<base64>" }'
# → { "ok": true, "jobId": "a91c...", "status": "processing" }
# poll with { "action": "poll", "jobId": "a91c..." }
# → { "ok": true, "status": "done", "reading": { ... } }Step 3 — Inventory Try-On
Matches the best piece per garment category from your synced inventory and renders them onto the photo. Async: submit, then poll.
/api/v1/inventory/tryonSubmit| Field | Type | Description |
|---|---|---|
readingrequired | StyleMeReading | The reading object returned by POST /api/v1/styleme. |
photoBase64required | string | Base64-encoded photo of the person to render the outfit onto. |
occasion | string | Optional occasion (e.g. "Client presentation") that shapes which pieces are shopped for. |
bodyType | string | Optional body type override, e.g. "M", "Petite". Defaults to the reading's own inferred bodyType. |
callbackUrl | string | Optional webhook. See Callbacks below. |
Response (submitted):
{ "ok": true, "jobId": "d81f...", "status": "processing" }/api/v1/inventory/tryonPollResponse when done:
{
"ok": true,
"status": "done",
"imageUrl": "https://...",
"outfitBreakdown": [
{
"category": "top",
"type": "matched",
"productId": "sku-001",
"productName": "Tailored Wool Blazer",
"imageUrl": "https://...",
"price": "USD 220",
"source": "inventory"
},
{
"category": "shoes",
"type": "invented",
"reason": "Your catalog had nothing in this category for the look.",
"brief": "Sleek black ankle boots with a low block heel.",
"bestMatch": {
"productId": "cj-9182",
"productName": "Leather Block-Heel Boot",
"shopName": "Studio Nine",
"imageUrl": "https://...",
"price": "USD 96",
"clickUrl": "https://...",
"explanation": "Closest real match for the designed silhouette."
}
}
]
}Every entry has a type."matched" is a real piece from your synced catalog, worn exactly as photographed."invented" means your catalog had nothing good for that category — the piece worn in the photo was designed by the AI instead of left empty, and (when available) bestMatch is a real, purchasable product that resembles it, offered purely as a "shop similar" suggestion — it is never the piece actually rendered on the photo.
Callbacks (webhooks)
runway-edit and inventory/tryon accept an optional callbackUrl on submit. When the job finishes (success or failure), we POST the same payload a completed poll would return — in addition to, never instead of, polling. Best-effort delivery with no retries, so keep polling as your source of truth.
// what we POST to callbackUrl on success:
{ "jobId": "...", "status": "done", "imageUrl": "https://...", "outfitBreakdown": [...] }
// on failure:
{ "jobId": "...", "status": "error", "error": "..." }callbackUrl must be a public http(s) URL — localhost and private network addresses are rejected.
Full example
# 1. Sync your catalog once (Step 1)
curl https://zukme.com/api/v1/inventory/products -X PUT \
-H "Authorization: Bearer zk_live_..." -H "Content-Type: application/json" \
-d '{ "products": [ { "id": "sku-001", "name": "Tailored Wool Blazer", "imageUrl": "https://...", "category": "outerwear" } ] }'
# 2. Get a StyleMe reading (Step 2), then submit a try-on
curl https://zukme.com/api/v1/inventory/tryon -X POST \
-H "Authorization: Bearer zk_live_..." -H "Content-Type: application/json" \
-d '{ "reading": { /* from POST /api/v1/styleme */ }, "photoBase64": "<base64>", "occasion": "Client presentation" }'
# → { "ok": true, "jobId": "d81f...", "status": "processing" }
# 3. Poll until done
curl https://zukme.com/api/v1/inventory/tryon -X POST \
-H "Authorization: Bearer zk_live_..." -H "Content-Type: application/json" \
-d '{ "action": "poll", "jobId": "d81f..." }'
# → { "ok": true, "status": "done", "imageUrl": "https://...", "outfitBreakdown": [...] }const KEY = process.env.ZUKME_API_KEY;
const BASE = "https://zukme.com/api/v1";
async function call(path, body) {
const res = await fetch(`${BASE}${path}`, {
method: path.endsWith("/products") ? "PUT" : "POST",
headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
});
return res.json();
}
await call("/inventory/products", { products: myCatalog });
const submitted = await call("/inventory/tryon", { reading, photoBase64, occasion: "Client presentation" });
let result;
while (true) {
result = await call("/inventory/tryon", { action: "poll", jobId: submitted.jobId });
if (result.status === "done" || result.status === "error") break;
await new Promise((r) => setTimeout(r, 2500));
}
console.log(result);Building against a different source?