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:
<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.
| Attribute | Effect |
|---|---|
data-key | Your publishable key. Required |
data-product | The 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-target | A CSS selector to mount the button in, instead of beside the script tag |
data-label | Button text. Defaults to "Try it on" |
data-platform | shopify 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-proxy | A 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 |
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 belowConsent
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.
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:
{
"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:
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" }'{ "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.
