Merchant app

Storefront widget

One script tag. A button next to Add to bag, and a dialog that does the rest.

The widget is what a shopper actually uses. It is a single script, no dependencies and no build step — a merchant pastes one tag next to their Add to bag button:

html
<script src="https://www.genfashion.app/embed/v1.js"
        data-key="sk_publishable_…"
        data-product="SKU-1234" defer></script>

It renders its own Try it on button beside the script tag, and opens a dialog over the page when clicked. The API it talks to is resolved from wherever this script was served, so a rebranded deployment needs no extra configuration.

Why the key in your page source is safe

data-key takes a publishable key (pk_…), which is public by definition — anyone viewing your page source can read it. What bounds it is the origin allowlist you configure for that key under Settings → API keys: a request from a page on a domain not on the list is refused. An empty allowlist is refused too, at creation — there is no "allow anywhere" state.

Never put a secret key (sk_…) here. It works server-to-server only; see the Try-On API.

Configuration

All of these can be set as data-* attributes on the script tag, or as query parameters on the script's own src — the second exists because a Shopify app-store install injects the widget with a bare src and nothing else to hang an attribute on.

AttributeEffect
data-keyYour publishable key. Required
data-productThe product on this page, by the externalId you gave it. Omit it and the button does not render — there is nothing to try on
data-targetA CSS selector to mount the button in, instead of beside the script tag
data-labelButton text. Defaults to "Try it on"
data-platformshopify reads the product id from the page's own Shopify analytics object and mounts next to the add-to-cart form automatically, retrying briefly if the theme renders it late
data-proxyA Shopify app proxy path (e.g. /apps/try-on). When set, the widget trades it for a short-lived session token instead of carrying a key — see below
js
window.GenFashion.open("SKU-1234");     // open the dialog from your own markup
window.GenFashion.mount(el, "SKU-1234"); // render a button into any element
window.GenFashion.shopperId();          // the per-browser id described below

Consent

The widget will not open a file picker until the shopper has ticked a consent checkbox — a pre-ticked box is the exact pattern GDPR calls out as not consent, so there is no default-on state to configure. Once agreed, the choice is remembered in localStorage and shown as "You agreed on \<date\>" rather than asked again. The consent record travels with the photograph on every call, and the backing endpoint refuses a request with none, before the photo is even decoded — see Privacy & compliance.

Shopper identity

There is no shopper account. The widget mints a random id on first use and keeps it in localStorage — a rate-limit key, never an authorization credential. It does not survive a cleared cookie or a different browser, which matters if you ever need to find a shopper's try-ons for an erasure request: pass your own customer id instead if you have one (see Sessions, below).

Building your own front end

If you are not using the packaged widget — a headless storefront, a native app — call the same endpoint the widget calls directly.

bash
curl -X POST "https://www.genfashion.app/api/storefront/v1/try-on" \
  -H "x-api-key: sk_publishable_your_key" \
  -H "X-Shopper-Id: a-stable-id-your-frontend-generated" \
  -H "Origin: https://shop.example.com" \
  -H "Content-Type: application/json" \
  -d '{
    "person": "data:image/jpeg;base64,…",
    "productIds": ["shirt-42"],
    "consent": { "version": 1, "at": "2026-09-18T09:00:00Z" }
  }'

Unlike the Try-On API, this call is synchronous — a shopper watching a product page is better served by one request they can watch than a poll loop, so the response carries the finished result directly:

json
{
  "generationId": "gen_eb56ae63…",
  "status": "COMPLETED",
  "imageUrl": "https://…signed…",
  "garmentPhrase": "blue and black check shirt",
  "durationMs": 45113
}

person is a base64 data:image/… string — there is no URL form here, since the photo comes straight from the shopper's device. Everything else — the catalogue-only rule on productIds, the 6-garment cap, screening running before any generation model and costing no credit on refusal — is shared with the Try-On API, because both run through the same pipeline.

Sessions, for an app-proxy install

A request signed with a bare publishable key has no identity for the shopper beyond whatever X-Shopper-Id it sends. If your integration can mint an id server-side — a Shopify app proxy request, which already carries a signed shop context — trade your secret key for a short-lived token bound to both that shopper and the storefront's origin:

bash
curl -X POST "https://www.genfashion.app/api/storefront/v1/sessions" \
  -H "x-api-key: sk_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{ "shopperId": "your-own-customer-id", "origin": "https://shop.example.com" }'
json
{ "token": "…", "expiresAt": "2026-09-18T09:15:00Z", "expiresIn": 900 }

Use that token as x-api-key on /storefront/v1/try-on instead of the publishable key. It cannot be lifted to another site — it is bound to the origin it was minted for — and it carries the shopper id you gave it, so an erasure request against that id finds every try-on made with it. This call must come from your server: it needs the secret key, and has no origin check of its own because a server request has no origin to check.