Reference

Changelog

What changed, when, and what it means for an integration.

Dated entries, newest first. Anything that would break an existing integration is called out as Breaking with what to do about it.

This is a young product and the entries say so. Where something was shipped wrong and corrected, the correction is listed rather than quietly folded into the next release — a changelog that only records additions is not much use for deciding whether to upgrade.

Erasure, webhooks, AI labels, and a job that had never run

Read this one if you run a storefront. The hourly job that deletes expired shopper photographs — /api/maintenance/purge — had never executed, from the day it was written until today. Vercel Cron issues GET; the route exported only POST, so every scheduled run answered 405. Everything an operator would check looked correct: a green deploy, a correct schedule entry, the job listed, and the route working perfectly when called by hand.

No photograph outlived its window. We checked the table before fixing it: two person images existed and neither had reached its expires_at, so nothing was retained past the promise. That is luck rather than design, and the marketing site had been saying "shopper photos auto-delete" throughout. The fix is a GET alias, the same one the health job had carried since it was written, and both are now covered by a cross-check where every scheduled job watches the others.

Added — erasure over the API. DELETE /api/tryon/v1/customers/:externalUserId removes a shopper's photograph, the result images made from it, and the identifiers tying records to them, and returns a receipt saying what went. GET on the same path shows what is held and the consent trail. It does not remove that a generation happened or what it cost — that is your billing record, not the subject's personal data. Previously a subject request meant writing to us and waiting for a person; that path still exists.

Added — outbound webhooks. Signed POST on generation.completed and generation.failed, configured under Settings → Webhooks. Verify with the X-Webhook-Signature header: HMAC SHA-256 over the raw body. Hash the bytes you received — re-encoding the parsed object changes them and the signature will never match. Retries run at 30s, 2m, 10m, 1h and 6h, then the delivery is given up.

Corrected — the AI disclosure now exists. The 2026-09-07 entry below says results carry no composited badge and no provenance metadata, and that "none of that is implemented". That was true when written and is no longer.

Two mechanisms, and they differ in whether you can turn them off. Provenance metadata is written into every result, always, and takes no flag — there is no setting for it, here or anywhere. The visible AI MODIFIED badge is composited by default and is configurable: per account under Settings → Workspace, or per request with output.aiLabel: false on the server API. The widget API does not accept the override, because it runs on a publishable key that ships in your page source and anyone reading it could otherwise strip the disclosure.

Both are recorded from what the compositor actually did rather than what it was asked to do, so a badge that failed to render is not recorded as applied. Treat any result generated before 2026-09-13 as unlabelled, whatever an older record says.

Still not C2PA, and the distinction is deliberate. What we write is an unsigned text field naming the model and marking the image machine-generated. C2PA is a signed manifest bound to a certificate a verifier can trace. Ours is neither. Anyone can strip or forge it. It is worth having and it is not proof — this is the second time this page has had to be precise about that, having once claimed more than was built.

Added — garment screening. Uploaded garment images are now screened before they reach a generation model, on a narrower set of categories than a person photograph. sexual is deliberately excluded: automated moderation flags lingerie and swimwear product shots, and those are categories this product exists to serve.

Changed — pricing. The site advertised four subscription tiers (FREE / STARTER / GROWTH / PRO) with monthly allowances and a "Remove branding" feature. None of them could be bought; there is no plan column and nothing bills monthly. The product sells credit packs, one try-on spends one credit, failed generations are never charged, and credits do not expire. The pages now show what your operator actually charges.

Added in the console. CSV export on Products and Logs honouring the filters on screen; filters and a detail view on Logs; paging on the credit ledger; and per-store sync exclusions so a synced catalogue can leave products out.

A server API, real cost accounting, and keys encrypted at rest

Added — the server Try-On API. /api/tryon/v1/ with POST /try-on, GET /generations/:id and GET /account/credits, authenticated by a secret key. The API reference previously documented endpoints that had never existed in any version; that page was rewritten to describe what is served, with the unbuilt parts listed as unbuilt rather than removed quietly.

Corrected — what a generation costs. Provider cost was recorded from a flat per-call constant rather than from what the provider billed. A two-call generation was recorded as $0.08 against roughly $0.25 actually billed — about 3x understated per generation, and about 6x on the compose call alone, which is where the money is. Every margin figure derived from it was wrong. Cost is now priced from the token counts in the provider's own response, and a generation that cannot be priced honestly is marked estimated rather than given a plausible number.

Corrected — `ai_label_applied`. The column defaulted to true, so historical rows claimed a label that was never composited. It now records what actually happened.

Changed — default model. New generations run on gpt-image-2.5-sunburst. Measured on one fixture it was the fastest of the pool at 45.1s against gpt-image-1's 55.9s, and no worse to look at. That is a change of default, not a quality gate result — the gate has still not been scored.

Security. Provider API keys are encrypted at rest; the database no longer holds one in the clear. Database functions were revoked from the anon and authenticated roles, so every call goes through the server rather than being reachable from a browser with a publishable key.

Storefront, Shopify and WooCommerce

Added. The storefront widget: one script tag, shadow DOM, no build step. It runs unchanged on a pasted snippet, a Shopify theme app extension and a WooCommerce install.

Added. The storefront API — POST /try-on, POST /sessions, GET /generations/:id — with two authentication modes: a publishable key bound to an origin allowlist, or a short-lived session token for platforms that can prove the request came through their storefront.

Added. Shopify app with OAuth install, catalogue sync and four webhooks. On Shopify the page carries no key at all: requests go through Shopify's app proxy.

Added. WooCommerce plugin using one-time install codes instead of a pasted key. The HTTP contract is verified; the PHP itself has not yet been run against a live WordPress install.

Added. API keys with origin allowlisting. A publishable key with no origins is refused at creation and at resolution. Only a hash is stored and the plaintext is shown once.

Fixed. The try-on dialog was being hidden on Shopify's Dawn theme, which ships div:empty { display: none }. Everything the widget renders lives in a shadow root, so the host element genuinely is empty in the light DOM.

Safety, money and accounts

Added. Photo screening before any generation model runs, failing closed — including when the screening service itself is unavailable.

Added. Person-image storage with real retention: a private bucket, ten-minute signed URLs, an expiry date in the storage path and an hourly sweep that deletes bytes before marking rows.

Added. Layered rate limits — per shopper, per staff user, per account, per platform — configurable in the UI, where the host's values are both the default and the ceiling.

Added. Credit purchase through Stripe (USD) and Razorpay (INR), both over plain REST. Money is reported per currency and never summed across them.

Added. Team invites, an account switcher, and roles that actually restrict spending and billing access.

Added. Email confirmation, password reset and Google sign-in, with the app owning the OAuth handshake rather than the auth host.

Fixed — security. /api/generate took the account id from the request body and never checked a session, so a POST naming any account could spend that account's credits. It now reads the account from the session cookie.

Fixed — security. Generation had no credit check and no floor on the balance, so an account at zero could generate indefinitely while its balance went negative.

Changed. The signup bonus dropped from 25 credits to 5. Real inference costs about $0.04 a call, so 25 was giving away $1–2 per signup.

Brand and marketing site

Added. The GenFashion brand, a revenue estimator, a platforms page, a blog, and booking and contact routes.

Changed. /how-it-works folded into the landing page. Old links redirect to the anchor.

Changed. The product name moved behind BRAND_NAME, covering everywhere a host client's customer meets it — the checkout line item, support email, page titles.

First release

Added. The admin platform, the try-on playground, the documentation site, the AI assistant with human escalation, and the data access layer: around 100 Postgres functions over PostgREST, with no inline queries and no Supabase SDK anywhere.