ZukMe
← Use My Own InventoryAPI Docs

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.com

Every 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

FieldTypeDescription
400Bad RequestMissing or invalid fields in the request body.
401UnauthorizedMissing, malformed, or revoked API key.
402Payment RequiredCredits exhausted (code CREDITS_EXHAUSTED), with your current looks/bonus/total balance in the body.
404Not FoundjobId does not exist (expired, wrong id, or never submitted).
429Too Many RequestsRate 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

FieldTypeDescription
artPhotoBase64requiredstringBase64-encoded source image (art, photo, or design) the outfit is built from.
outfitNamerequiredstringShort name for the outfit, e.g. "Tailored Blazer Set".
outfitDirectionrequiredstringFree-text description of the garment(s): cut, pieces, silhouette. Up to 3000 characters.
sceneNamerequiredstringShort name for the scene/location, e.g. "Golden Hour Rooftop".
styleDirectionrequiredstringFree-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 } | nullOptional, captured on the job for future use. Never alters generation.
callbackUrlstringOptional webhook. See Callbacks below.

Response (submitted):

{ "ok": true, "jobId": "b3f1...", "status": "processing" }

POST /api/v1/runway-edit · Poll

FieldTypeDescription
actionrequired"poll"Selects the poll branch.
jobIdrequiredstringThe 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

FieldTypeDescription
photoBase64requiredstringBase64-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

FieldTypeDescription
validbooleanFalse if the photo could not be read (see rejection_reason).
rejection_reasonstring | nullExplanation when valid is false.
styleTitlestringThe person's style archetype, e.g. "The Modern Minimalist".
styleSubtitlestringOne-line elaboration on the style title.
readingstring[]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.
tagsstring[]Short style keyword tags.
genderstring | undefinedInferred gender, for downstream use.
bodyTypestring | undefinedInferred 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

FieldTypeDescription
productsrequiredInventoryProduct[]Your full product list — replaces whatever was previously synced. Up to 5000 items.

The InventoryProduct object

FieldTypeDescription
idrequiredstringYour own stable product id (e.g. SKU).
namerequiredstringProduct name.
imageUrlrequiredstringPublicly fetchable product photo URL.
pricestringDisplay price, e.g. "USD 84".
descriptionstringFree text — helps matching quality.
categorystringFree text, e.g. "top", "shoes" — helps matching quality, not required to be an exact match.
linkstringProduct 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

FieldTypeDescription
readingrequiredStyleMeReadingThe reading object returned by POST /api/v1/styleme.
photoBase64requiredstringBase64-encoded photo of the person to render the outfit onto.
occasionstringOptional occasion, e.g. "Client presentation" — shapes which pieces are shopped for.
bodyTypestringOptional body type override, e.g. "M", "Petite". Defaults to the reading's own inferred bodyType.
location{ lat: number, lon: number } | nullOptional, captured on the job. Never alters matching.
callbackUrlstringOptional 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': [...]}

ZukMe Global Network

Africa

Ghana: GS-0168-9885, Attah Mills Street, Opp. Downtown Pub, Old Barrier, Accra, Ga South, Greater Accra

Rwanda: KK 734 St, Kigali, Rwanda

+233(0)505807777

North America

United States 7901 4th St N, Suite 300, St Petersburg, Florida 33702, United States

+1 850 5170 671

Resources

Need help getting started?

Contact Us

© 2026 ZukMe LLC. All rights reserved.