Skip to content

Case study · hero · 2026-05 to present

Kintsu AI Consultation Tool

Built an ephemeral AI skin assessment that takes a selfie, maps concerns to real treatments, and redirects to booking. No database. No auth. No stored photo.

Role
Product designer, architect, and solo builder
Company
Kintsu Medical Aesthetics
Dates
2026-05 to present
Bucket
current venture
Consultation step of the Kintsu AI tool with the Dark spots / uneven tone tab active: a Moderate severity badge, post-inflammatory hyperpigmentation education written for Fitzpatrick III to VI skin, a Worth exploring section recommending the Kintsu Glow HydraFacial, a Products to ask about card for a Murad serum tested on Fitzpatrick III to VI, a Was this helpful thumbs widget, and a sticky Continue to booking button
Kintsu Medical Aesthetics · AI Consultation Tool · try.kintsumedical.comThe result step. Each concern tab carries a severity read, Fitzpatrick-aware education, a vetted service recommendation, and a product to ask about. The sticky booking CTA is always in reach.

TL;DR

One-line outcome. Built an ephemeral AI skin assessment that takes a selfie, maps concerns to real treatments, and redirects to booking. No database. No auth. No stored photo.

Role, dates, scope. Product designer, architect, and solo builder. Specification authored May 2026, build in progress. Standalone tool at try.kintsumedical.com, separate repo from the main Kintsu site. Extends the Kintsu Medical Aesthetics engagement (see Kintsu Medspa).

Three design decisions worth naming.

  • Ephemeral by architecture, not by policy: no database, no photo storage. The HIPAA surface is reduced to zero at the design layer.
  • AI simulation feature-flagged off at launch pending attorney and image-model sign-off, so every other feature ships and works on opening day.
  • Catalog filter as a first-class safety layer: the API only returns recommendations from a static vetted slug list, so GPT-4o cannot hallucinate a treatment the practice does not offer.

Context

A pre-launch medspa in a competitive market needs to convert curious visitors into booked consultations. The gap is real: a potential patient lands on the site, sees services they are unsure apply to their skin, and leaves without booking. The ageless.ai category shows the model is viable.

The constraint on this project is not technical. It is legal and clinical. An AI-generated skin analysis that presents as advice from a physician-led practice carries liability. The tool had to be useful enough to build intent, clear enough to survive a regulatory review, and honest enough that a patient with Fitzpatrick V skin understood it was designed with them in mind, not adapted from a product calibrated for Fitzpatrick I.

Mandate

Build an ephemeral AI consultation tool that captures a selfie and short intake form, runs a GPT-4o vision analysis, surfaces an aesthetic assessment and service recommendations mapped to the real Kintsu catalog, and redirects the user into the Zenoti booking flow with pre-populated intent. Store nothing. Use no login. Shut the photo out of the system the moment the analysis completes.

An AI-generated "after" simulation using gpt-image-1 image edits, watermarked via sharp, ships behind a feature flag. Attorney and image-model review clears the flag.

Scope

Solo product design and engineering. New repo, deployed to Vercel at try.kintsumedical.com. Backend is a single Vercel serverless function at /api/analyze. Frontend is React 19, Vite 7, TypeScript, and Tailwind CSS 4. No database. No auth. No third-party analytics inside the tool. Conversion tracked via the Zenoti redirect URL with ?src=ai-tool.

Wizard · Five Steps · Nothing Stored

1LANDINGValue prop + ephemeralnotice. No login.2INTAKEAge range + concernpills. ~10 seconds.3CONSENT + UPLOADUpload locked untilconsent checked. Gateenforced in UI +server.4AI ANALYSISVision model reads thephoto. Photo clearedon advance.5RESULTS -> BOOKINGRecommendations +Zenoti redirect(?src=ai-tool).EPHEMERAL: NO DATABASE, NO PHOTO STORAGE
Five steps, no login, nothing persisted. The consent gate is enforced in the UI and again server-side; the photo is cleared the moment the user advances.

THE FLOW · LIVE UI

The wizard in the running tool.

01 / 04
Landing
Intake
Consent + upload
Booking

Landing

Value prop and ephemeral notice above the fold. Photo is never stored. This is the first thing the user reads.

Acne scars tab showing Subtle severity badge, educational copy about PIH risk for Fitzpatrick IV skin when considering microneedling, and a Worth exploring section recommending medical-grade microneedling with physician-supervised depth protocol
Consultation step · Acne scars tabThe Acne scars tab surfaces Fitzpatrick IV-specific safety copy before the service recommendation. PIH risk for microneedling is named explicitly, not buried in a disclaimer.

Key decisions

Ephemeral pipeline · nothing persisted

The image travels in the request body, passes through the model, and the result returns to the browser. No write to disk, no row in a table. Storage is impossible by architecture, not by policy.

Ephemeral by architecture, not by policy. The easy path was a database with a privacy policy that said "we delete photos after 24 hours." I rejected it. A policy is only as strong as its enforcement mechanism, and the breach surface is real regardless of the retention window. The harder path is architecture that makes persistence physically impossible: the image base64 travels in the POST body, passes through OpenAI, and the response returns to the browser. No write to disk. No write to a table. The api/analyze.ts endpoint is a sequential function pipeline with no persistence layer between rate-limit check and the final Response return.

Catalog filter as a first-class safety layer. The model is prompted with a strict system prompt: no diagnostic language, Fitzpatrick-aware framing, recommendations framed as "people with similar concerns often explore." But the model can still hallucinate. Both filterObservations() and filterRecommendations() run before the response leaves the server. The first guards the concern/zone vocabulary, the second drops any service_slug not present in the bundled services.json static catalog. If the model returns laser-resurfacing and the practice does not offer laser, that slug never reaches the client. The filter is tested with a unit test that asserts hallucinated slugs are dropped and the valid ones pass through unchanged.

Model Output

Server-Side Filter

Client

STATIC VETTED ALLOWLISTkintsu-glow-hydrafacialmedical-microneedlinglaser-resurfacingtopix-replenix-serumfilterObservations()GUARDS CONCERN + ZONE VOCABULARYfilterRecommendations()DROPS SLUGS NOT IN SERVICES.JSONkintsu-glow-hydrafacialmedical-microneedlingtopix-replenix-serumlaser-resurfacingDROPPEDNOT OFFERED
Two guards run server-side before any response leaves the function. A hallucinated slug the practice does not offer is dropped, never reaching the patient.

Feature flag for the simulation, not a deferred launch. The before/after simulation is the most engaging and most legally exposed feature. I could have blocked the full launch until legal cleared it. Instead, ENABLE_SIMULATION=false in the Vercel environment disables exactly that one path in api/analyze.ts and in ResultsStep.tsx. The consent gate, intake form, vision analysis, recommendation cards, and Zenoti redirect all ship and work on day one. The flag is a binary environment variable. Turning it on requires one change in the Vercel dashboard, no redeploy.

Turnstile bot protection without login friction. The /api/analyze endpoint needed protection against automated scraping (cost risk: OpenAI vision API calls are not cheap), but a login wall would have killed conversion. Cloudflare Turnstile solves this: an invisible challenge a real browser passes silently, a bot fails. In development without the key configured, the verification function short-circuits to true so the dev loop is never blocked. In production, a failed Turnstile challenge returns a 400 before the OpenAI client is initialized.

HIPAA-safe redirect design. Service slugs and consent metadata travel in the Zenoti redirect URL (?interest=microneedling,botox&src=ai-tool&consent=v1&consent_ts=2026-05-31T10:00:00Z). Patient identifiers do not. The buildBookingUrl() function accepts only selectedSlugs, consentVersion, and consentTimestamp. A unit test asserts no email-pattern strings can appear in the output URL. The HIPAA rule from the main Kintsu site (GA4 page_location captures the full URL on every pageview) is mirrored here at the architectural layer, not the policy layer.

Skin of color first, not as an afterthought. The system prompt names Fitzpatrick types III through VI explicitly. It instructs the model to flag PIH (post-inflammatory hyperpigmentation) risk when recommending microneedling or RF procedures for darker skin tones. The services.json catalog includes a skinOfColorNote field on each treatment entry, surfaced on the recommendation card. The patients Kintsu was founded to serve are the design target, not the edge case.

What changed

  • The patient intake-to-booking funnel has a middle step that produces intent. A visitor who would have left without booking leaves with a recommendation list and a pre-populated booking link.
  • The AI consultation surface handles Fitzpatrick III through VI by design, not by disclaimer. The system prompt, the service catalog notes, and the PIH risk flags encode that intent into the product behavior.
  • The simulation feature is ready to activate, not a future project. One environment variable turns it on the moment the practice has attorney clearance.

Measurable outcomes

Currently in development. Outcomes to track after launch:

  • Conversion rate: visitors who complete the wizard and click the Zenoti booking link.
  • Booking attribution: Zenoti consultations with ?src=ai-tool in the referral string.
  • Step drop-off: where users abandon across consent gate, upload, processing, results, and recommendations.

MOBILE · 390px

Full wizard on phone.

  1. 01 / 03Landing · mobile
    Landing · mobile
    Serif heading wraps to two lines. Disclaimer box spans full width. CTA is thumb-reachable.
  2. 02 / 03Intake · mobile
    Intake · mobile
    Age pills wrap to two rows. All ten concern chips accessible without scrolling.
  3. 03 / 03Consent · mobile
    Consent · mobile
    Upload stays locked until consent is checked. The gate holds at body size, thumb-reachable.
Mobile view of the consultation step showing the Dark spots tab with Moderate badge, PIH educational copy, a recommendation paragraph, a product card with AM/PM label and Tested on Fitzpatrick III-VI italic note, a Was this helpful widget, and the sticky Continue to booking footer
Consultation step · Mobile · service cardsService and product recommendations scroll naturally on mobile. The sticky booking footer stays fixed. No dead-end state where the user is left without a next action.

Leadership lens

The instinct on a privacy-sensitive product is to add policy: a privacy page, a data-retention schedule, a terms update. Policy without architecture is cosmetic. I chose the architecture first, made storage impossible, then wrote the disclaimer footer. When the practice attorney reviews this tool, the most persuasive thing in the packet is not the privacy policy. It is the fact that there is no database to breach.

The feature flag is the same logic applied to the simulation risk. Scope the legally exposed feature into a flag. Ship everything else on time. The risky path launches when the gate clears, not when the whole product launches. This is not risk deferral; it is risk isolation.

What I did with my hands

Player-coach proof, all authored personally:

  • The full implementation plan (13 tasks, TDD structure, unit tests written before each module): docs/superpowers/plans/2026-05-30-kintsu-ai-consultation-tool.md.
  • The system prompt in api/lib/prompts.ts: clinical framing, Fitzpatrick-aware instructions, JSON schema contract for structured output, inline slug allowlist enforcement, PIH risk rules for Fitzpatrick IV through VI.
  • The ephemerality contract in api/analyze.ts: no side effects between the rate-limit check and the final Response return. Reviewable as a linear 60-line pipeline.
  • The service catalog in src/data/services.json: 28 face services with concern mappings, descriptions, and skinOfColorNote fields authored from the Kintsu treatment menu. Body, IV, and weight-loss services were intentionally excluded. This is a face tool.
  • The Topix take-home product catalog in src/data/products.json: 17 curated retail products mapped per concern, selected server-side (never model-generated, which keeps product recommendations out of the attorney-gate risk surface).
  • Upload and webcam capture, unified through a shared encode helper, with a fake ring light (the camera screen in full-white bg-white mode acts as a fill light, the dominant accuracy lever for the vision model).
  • The redirect URL builder in src/lib/redirect.ts, with a unit test asserting that no email-pattern string can appear in the output URL.
  • The watermark module in api/lib/watermark.ts: SVG overlay composited onto the simulation image via sharp, labeled "AI Simulation. Not a real result. Individual results vary."

AI threading

The product is an AI feature, not an AI-assisted build. Gemini 2.5 Pro runs the vision analysis, currently active, A/B'd against gpt-4.1 via the feedback loop. Both providers are wired through the Vercel AI SDK using generateObject with a Zod schema, so the structured output contract is validated at the SDK layer rather than hand-parsed. Provider and model are runtime-selected via a VISION_MODEL env var (openai:gpt-4.1 | google:gemini-2.5-pro). Gemini was chosen because it showed stronger spatial and skin-tone discrimination on the concern types that matter most for Fitzpatrick III to VI skin.

Images are sent at detail: 'high'. An earlier detail: 'low' path was a regression. It collapsed every photo to one 512px pass and discarded the tone, texture, and pigment detail the tool exists to read. Fixing it required tracking down a non-obvious provider compatibility constraint: Gemini's API requires images as a raw Buffer, not a data:image/...;base64, data URL. The base64 prefix causes a silent decode failure. The system prompt also had to move from a role:'system' message into the SDK's dedicated system parameter, which Gemini silently ignores when passed as a message. Neither issue surfaced until real integration testing.

The system prompt is the design artifact of this work. It is not a prompt that asks the model to "analyze skin" and returns free-form text. It contracts a specific JSON schema (assessment.summary, assessment.observations[].concern, assessment.observations[].note, assessment.fitzpatrick_estimate, recommendations[].service_slug, recommendations[].why, recommendations[].skin_of_color_note), forbids diagnostic language by name, lists the slug allowlist inline, and instructs the model to default conservative on Fitzpatrick estimation when skin tone is ambiguous in the photo. A model that follows the contract returns structured, usable data. A model that breaks the contract has its hallucinated slugs dropped by filterObservations() / filterRecommendations() before the client sees the response. Two separate guards, one for concern/zone vocabulary, one for service slugs.

The simulation path (feature-flagged off) uses gpt-image-1 image edits: the uploaded photo as the base, a conservative prompt capped at three concerns, then sharp watermarking before the base64 payload leaves the server.

The build itself was implemented with Claude Code using a task-by-task TDD plan: unit tests written before implementation, an integration test with mocked OpenAI before the endpoint was assembled, and Playwright e2e covering the consent gate, the ephemerality invariant (photo cleared on step advance), and the Zenoti redirect.

Reflection

The hardest constraint to hold was not the HIPAA rule or the legal exposure on the simulation. It was the Fitzpatrick concern. Skin analysis AI that treats Fitzpatrick I as the default and applies the same confidence to Fitzpatrick V is not a neutral choice. It is a product decision that excludes the patient demographic Kintsu was founded to serve. The system prompt, the skinOfColorNote fields, and the PIH risk flags are not compliance checkboxes. They are the design intent of the product.

Running this again I would get the attorney into the review loop before the system prompt was finalized, not after. The feature flag approach is correct but it is expensive if the flag stays off for months while the simulation prompt is revised to satisfy a late legal review. Shorter loop: draft the prompt, review it with counsel and the Medical Director together, ship the prompt and the flag in one pass.