Use My Own Inventory Docs
Full request/response schemas, error codes, and examples for the Use My Own Inventory product — inventory sync, Runway Edit, StyleMe, and match + render against your own catalog. Looking to match against ZukMe's marketplace instead? See the Pull from Marketplace Docs.
Base URL
https://zukme.comEvery endpoint below is relative to this base URL. For example, POST /api/v1/inventory/tryon meansPOST https://zukme.com/api/v1/inventory/tryon.
Authentication
Every request must include your API key as a bearer token. Create and manage keys in the API Dashboard. The raw key is shown once at creation; only its hash is ever stored, so if it's lost you must revoke it and create a new one.
Authorization: Bearer zk_live_<your key>A missing, malformed, or revoked key returns 401:
{ "error": "Invalid or missing API key. Pass it as \"Authorization: Bearer zk_live_...\"." }Rate limits
Each API key is limited to 30 requests per minute (submit and poll requests both count). Exceeding it returns 429:
{ "error": "Rate limit exceeded.", "retryAfterSeconds": 12 }Credits
API usage draws from the exact same credit balance as the ZukMe app — the same balance the Pull from Marketplace product shares too — with no separate metering system. Syncing your inventory never costs a credit. Each successful Runway Edit, StyleMe, or Inventory Try-On submission costs 1 credit, deducted when the job is submitted. If generation fails, the credit is refunded automatically. Polling for a job's status never costs a credit. See pricing for subscription tiers, or your account's balance any time via 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
}Runway Edit
Generates a photorealistic fashion editorial image: a model wearing an outfit built from your source art/photo, in a scene and style you direct. Runs as an async job: submit, then poll until it's done (generation typically takes 30 to 90 seconds across its internal stages).
POST /api/v1/runway-edit · Submit
| Field | Type | Description |
|---|---|---|
artPhotoBase64required | string | Base64-encoded source image (art, photo, or design) the outfit is built from. |
outfitNamerequired | string | Short name for the outfit, e.g. "Tailored Blazer Set". |
outfitDirectionrequired | string | Free-text description of the garment(s): cut, pieces, silhouette. Up to 3000 characters. |
sceneNamerequired | string | Short name for the scene/location, e.g. "Golden Hour Rooftop". |
styleDirectionrequired | string | Free-text mood/style direction for the scene and shot. Up to 2000 characters. |
size | "S" | "M" | "L" | "XL" | "XXL" | "XXXL" | Model build size. Defaults to "M". |
gender | "Male" | "Female" | "Neutral" | Model gender direction. Defaults to "Neutral". |
ageGroup | "Adult" | "New Wave" | "New Wave" generates a teen model profile. Defaults to "Adult". |
location | { lat: number, lon: number } | null | Optional, captured on the job for future use. Never alters generation. |
callbackUrl | string | Optional webhook. See Callbacks below. |
Response (submitted):
{ "ok": true, "jobId": "b3f1...", "status": "processing" }POST /api/v1/runway-edit · Poll
| Field | Type | Description |
|---|---|---|
actionrequired | "poll" | Selects the poll branch. |
jobIdrequired | string | The jobId returned by the submit call. |
Response while processing:
{ "status": "processing", "stage": "t2i" }Response when done:
{ "ok": true, "status": "done", "imageUrl": "https://..." }Response on failure (credit already refunded):
{ "status": "error", "error": "The AI model failed on this image. Please try again." }StyleMe
Generates a personalized style reading from a photo: a stylist-grade read on colouring, proportions, and aesthetic. Feeds directly into Inventory Try-On below. Also an async job: submit, then poll until done.
POST /api/v1/styleme · Submit
| Field | Type | Description |
|---|---|---|
photoBase64required | string | Base64-encoded photo of the person to generate a reading for. |
Response (submitted):
{ "ok": true, "jobId": "a91c...", "status": "processing" }POST /api/v1/styleme · Poll
Same poll shape as Runway Edit. Response when done:
{ "ok": true, "status": "done", "reading": { /* StyleMeReading, see below */ } }The reading object
| Field | Type | Description |
|---|---|---|
valid | boolean | False if the photo could not be read (see rejection_reason). |
rejection_reason | string | null | Explanation when valid is false. |
styleTitle | string | The person's style archetype, e.g. "The Modern Minimalist". |
styleSubtitle | string | One-line elaboration on the style title. |
reading | string[] | Paragraphs of the full style reading. |
personality | { trait, level, description }[] | Personality trait breakdown, level 0–100. |
palette | { hex, name }[] | Colours that suit this person's colouring. |
outfits | { name, occasion, pieces, why }[] | Suggested outfit concepts. |
tags | string[] | Short style keyword tags. |
gender | string | undefined | Inferred gender, for downstream use. |
bodyType | string | undefined | Inferred body type, e.g. "M", "Petite". |
Inventory Sync
Push your product catalog here before calling Inventory Try-On below. A full replace — whatever you send becomes your entire matchable catalog. No credits are spent syncing.
PUT /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 — helps matching quality. |
category | string | Free text, e.g. "top", "shoes" — helps matching quality, not required to be an exact match. |
link | string | Product page URL on your own site. |
Response:
{ "ok": true, "count": 128 }GET /api/v1/inventory/products
Returns whatever is currently synced, to confirm a push landed correctly:
{ "ok": true, "products": [ { "id": "sku-001", "name": "Tailored Wool Blazer", ... } ] }Inventory Try-On
Takes a StyleMe reading and a photo, matches the best piece per garment category from your synced inventory, and renders the result onto the photo. Async job: submit, then poll.
POST /api/v1/inventory/tryon · Submit
| 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" — shapes which pieces are shopped for. |
bodyType | string | Optional body type override, e.g. "M", "Petite". Defaults to the reading's own inferred bodyType. |
location | { lat: number, lon: number } | null | Optional, captured on the job. Never alters matching. |
callbackUrl | string | Optional webhook. See Callbacks below. |
Response (submitted):
{ "ok": true, "jobId": "d81f...", "status": "processing" }POST /api/v1/inventory/tryon · Poll
Same poll shape as Runway Edit. Response when done:
{
"ok": true,
"status": "done",
"imageUrl": "https://...",
"outfitBreakdown": [
{
"category": "top",
"productId": "sku-001",
"productName": "Tailored Wool Blazer",
"imageUrl": "https://...",
"price": "USD 220",
"source": "inventory"
}
]
}If a category has no good match in your current inventory, it's simply omitted from the look rather than invented — sync more products in that category and try again.
Callbacks (webhooks)
runway-edit and inventory/tryon both accept an optional callbackUrl in the submit request. When set, wePOST the same payload a completed poll would return to that URL once the job finishes — success or failure — in addition to, never instead of, polling. Delivery is best-effort: we don't retry or track failed deliveries, 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.
Job polling
Every submit endpoint returns a jobId and runs asynchronously, since generation involves multiple internal AI stages that don't fit in one request/response cycle. Poll the same endpoint every 2 to 3 seconds until you getstatus: "done" orstatus: "error". A job that returns404 either never existed or has expired. Treat it as failed.
Full example: curl
# 1. Sync your inventory — see Inventory Sync above
# 2. Get a StyleMe reading — see the StyleMe section above
# 3. Submit
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" }
# 4. 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..." }'
# → { "status": "processing", "stage": "edit" } (repeat)
# → { "ok": true, "status": "done", "imageUrl": "https://...", "outfitBreakdown": [...] }Full example: Node.js
const KEY = process.env.ZUKME_API_KEY;
const BASE = "https://zukme.com/api/v1/inventory/tryon";
async function call(body) {
const res = await fetch(BASE, {
method: "POST",
headers: {
Authorization: `Bearer ${KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
return res.json();
}
const submitted = await call({
reading, // from POST /api/v1/styleme
photoBase64,
occasion: "Client presentation",
});
let result;
while (true) {
result = await call({ action: "poll", jobId: submitted.jobId });
if (result.status === "done" || result.status === "error") break;
await new Promise((r) => setTimeout(r, 2500));
}
console.log(result); // { ok: true, status: "done", imageUrl: "...", outfitBreakdown: [...] }Full example: Python
import os, time, requests
KEY = os.environ["ZUKME_API_KEY"]
BASE = "https://zukme.com/api/v1/inventory/tryon"
headers = {"Authorization": f"Bearer {KEY}"}
submitted = requests.post(BASE, headers=headers, json={
"reading": reading, # from POST /api/v1/styleme
"photoBase64": photo_base64,
"occasion": "Client presentation",
}).json()
while True:
result = requests.post(BASE, headers=headers, json={
"action": "poll", "jobId": submitted["jobId"],
}).json()
if result["status"] in ("done", "error"):
break
time.sleep(2.5)
print(result) # {'ok': True, 'status': 'done', 'imageUrl': '...', 'outfitBreakdown': [...]}