In short: a before/after skin score can rise because the light changed, not the skin, and a brand's app would show that as a product result. Claim Guard checks retake noise, capture light and product type before the badge appears: on 12 held-out photos it blocked 64 of 64 pairs where the light changed.
Inspiration
Claim Guard lets brands that embed YouCam skin analysis show only the before/after badges they can defend, and it protects the credibility of YouCam scores in client apps.
I took a ceiling-light shot and a desk-lamp shot in the same room, minutes apart, with no skincare in between; the phone also sat in a different spot. YouCam Skin Analysis scored moisture 70 under the ceiling light and 75 under the lamp, so a before/after app would show "Moisture +7%". YouCam scored each photo faithfully: the same file sent twice returns identical scores. The photo changed, not the skin.
I then took 27 selfies of my own face in two sessions to see how much the scores move when nothing changes. Retaking the same photo under the same light moves the scores a little. Changing the light moves them a lot: a different room raised average moisture from 73.3 to 80.0 (+9.2%), and in one room, four desk-lamp shots and four ceiling-light shots taken minutes apart averaged 75.3 and 70.8 (+6.4% from ceiling to lamp).
A before/after score in a brand's app is a claim about the brand's product, and regulators ask for evidence behind cosmetic claims:
- EU: claims must be backed by adequate and verifiable evidence (Commission Regulation (EU) No 655/2013, Annex, points 3 and 4).
- US: advertisers need a reasonable basis for an objective claim before the ad runs (FTC Policy Statement Regarding Advertising Substantiation).
- Taiwan: cosmetic promotion must not be false or exaggerated, with fines of NT$40,000 to 200,000 (Cosmetic Hygiene and Safety Act, Articles 10 and 20), and a claim with insufficient evidence counts as false or exaggerated (Regulations Governing Criteria for the Label, Promotion, Advertisement ... of Cosmetic Products, Article 3).
- UK: the Advertising Standards Authority banned a skincare ad quoting "47% fewer fine lines" next to before/after photos it saw no evidence for (ASA ruling on Rejuvenex Direct UK Ltd, 2012), and cleared one that quoted a skin-imaging wrinkle score backed by a controlled, blinded study (ASA ruling on The Perfect Cosmetics Company Ltd, 2016).
Brands that embed YouCam skin analysis need a way to know when a score change is safe to show.
What it does
Claim Guard sits between YouCam skin analysis and the badge a beauty app shows. For each metric (moisture, pore, wrinkle, radiance) it runs five checks in order, and the first that applies decides:
- Same file. Two identical files mean nothing changed.
- Claim type. When the brand names the product category, a metric that category can't change is blocked. Foundation is makeup, so it can't claim moisture.
- Capture conditions. It measures both photos for face brightness relative to the background, backlight, and light colour on the background beside the head. If the light differs, every metric is blocked.
- Noise floor. The raw score change must be larger than what retaking the same photo produces (MDC95, pooled from five sets of retakes: moisture 7.10, pore 8.93, wrinkle 1.51, radiance 3.83).
- Display change. The badge shows the display score, so that score must go up by at least 1 point.
Every decision comes with a reason and is written to an audit log with the limits used, exportable as CSV.
The live site shows real YouCam results from my photos:
- Ceiling-light shot and desk-lamp shot, taken minutes apart (the opening case). Raw moisture moved +12.4 against a noise floor of 7.1, so a noise check alone would show the badge. Claim Guard blocks it: relative to the background the face is 23.2% darker, and the background light colour is 7.4% warmer.
- Same light, nothing changed. Every metric is within noise.
- Real moisturizer, same lamp, about 20 minutes later. The capture check passes. Raw moisture moved +1.0, inside the noise floor, so there is no moisture badge. Pore rose, but this policy does not let a moisturizer claim pore. Radiance fell by more than the noise floor, which is reported as a real drop with no badge.
- Virtual foundation (positive control). I used YouCam Makeup Virtual Try-On to put foundation on one photo, with the lamp and camera fixed. "Radiance +11%" is allowed, labelled "Appearance with product on". Moisture also rose (raw +13.72), but foundation is makeup, so that claim is blocked by claim type.
Other parts:
- Try your own photos. The capture check runs first and is free. Paid skin analysis runs only after the user ticks a consent box. "Try with sample photos" runs the free check on two demo photos and shows their real YouCam responses, cached from 2026-10-13, so it spends no credits.
- API for brands.
POST /api/v1/claim-checktakes the YouCam scores a brand already has, both photos and an optionalproduct_category, and returns a verdict, badge and reason per metric. It makes no YouCam call. A copy-paste client is inexamples/claim-check.ts. - MCP server.
npm run mcpexposes two tools,claim_checkandexplain_thresholds, so an assistant can run YouCam's own MCP server and then check the result before showing it.
How I built it
- YouCam APIs. Skin Analysis v2 (SD) with four concerns: moisture, pore, wrinkle, radiance (9 units per photo). Makeup Virtual Try-On with the foundation effect (1 unit) for the positive control. Flow: upload with
/s2s/v2.0/file, start the task, poll until success. - Data. 15 selfies on the first evening under three setups (same desk spot, window behind the head, ceiling light in another room), then 12 more in a later session (ceiling light, desk lamp, and the same lamp after a moisturizer). Raw responses and full-size photos stay local; the committed data is generated from them by a script.
- Noise floor. The standard deviation is pooled within five sets of same-setup retakes, so each set keeps its own mean and a light change between sets never counts as noise. MDC95 = 1.96 × √2 × SD.
- Capture check.
sharpmeasures a fixed central face box and the background strips beside the head on each upload. No photo leaves the server for this step. - App. Next.js 15 (App Router), React 19 and TypeScript on Vercel. Tests in Vitest cover the noise model, the capture check, the claim policy, the API routes, the MCP server, and a check that every number quoted in the README and docs matches
data/scores.json.
Protecting YouCam credits on the public demo:
- The capture check runs first and costs nothing; mismatched photos need an extra confirmation before any paid call.
- Paid analysis requires consent, enforced on the server (
consent=1). - Daily caps: 20 paid analyses per day across all users and 4 per IP. Paid calls are reserved before the request goes out, so parallel requests can't overshoot.
- Results are cached by SHA-256 of the photo for 24 hours, so the same photo is analysed once.
- The demo cases and the sample photos use saved YouCam responses and always work, even when the daily cap is used up.
LIVE_ANALYSIS_ENABLED=falseturns paid analysis off without a code change.- The API key stays on the server and is never logged or sent to the browser. Uploads are checked by content, size (4 MB per photo) and resolution, and every image route has a per-IP rate limit.
Challenges I ran into
- My first noise floor was too tight. It came from six photos taken one evening. On the later session's retakes, 6 of 18 same-setup pairs moved moisture past it and 10 of 18 moved wrinkle past it, where about 1 in 20 is expected. I switched to an SD pooled over five sets of retakes. Those photos now help set the floor, so that check is no longer held out.
- The first capture check blocked a real moisturizer. It read light colour from the face. After washing and moisturizer the face measured redder, and the check took that as warmer light: on the later photos, only 10 of 32 same-lamp before/after pairs passed. Version 2 measures light colour on the wall beside the head and divides face brightness by background brightness. The limit values (10%, 15%, 2%) did not change. On the same photos, 26 of 32 pairs now pass. Because those photos shaped version 2, I report that number as a re-run on data that informed the design.
- The positive control first mixed two changes. Try-On rejects images over 1920 × 1920, so I resized the photo. Comparing the try-on output to the original file mixed the resize with the foundation. I re-analysed the exact resized file as the "before"; that withdrew a pore claim that was only the resize.
- YouCam scores are all higher-is-better. "Wrinkle +12%" reads as "more wrinkles", so badges name the improvement: "Smoother skin (wrinkle score) +12%".
Accomplishments that I'm proud of
- The capture limits were set on the first 15 photos only. On 12 photos from a later session, version 1 blocked 64 of 64 pairs where the light changed and passed 32 of 36 same-setup retake pairs. Those are held-out numbers.
- The check lets a real change through: in the positive control, "Radiance +11%" is allowed and labelled as an appearance claim.
- The moisturizer result, reported as is. One application did not move moisture past retake noise, and the site says so.
- Every number on the site, in the README and in the docs comes from one data file with one rounding rule, and a test fails if a quoted number drifts.
What I learned
- A skin score change can be produced by the light alone. A desk-lamp shot and a ceiling-light shot taken minutes apart, with the phone moved, were enough for a "+7%" badge.
- Retake noise differs a lot between evenings and setups, so a floor measured once is not enough.
- The display score is compressed. Raw scores show the movement, so Claim Guard tests on raw and reports in the display score.
- From pixels alone, makeup glow and a brighter lamp look alike. Measuring the background beside the face separates light changes from skin changes better than measuring the face.
What's next
- Place the measurement box on a detected face instead of a fixed central box, and reject photos with no face.
- A fresh set of photos to get a held-out figure for version 2 of the capture check.
- Recalibrate the noise floor and capture limits with retakes from more people, skin tones and phones. Today it is one face and one phone.
- Move the daily caps and cache to a shared store such as Vercel KV, so they hold across server instances.
- Let a brand's compliance team edit the claim policy per product line and keep the audit log in its own store.
Limits (stated on the site too)
- One face, one phone, two sessions. The capture limits come from three setups.
- The version 2 capture numbers and the pooled noise floor use photos that also shaped them, so they are in-sample.
- The positive control is a simulated product effect on one photo, not treated skin.
- The moisturizer case is one application after about 20 minutes on one person. It says nothing about what the product does over weeks.
- Claim Guard does not make a claim compliant. It catches common ways a before/after number misleads.
Links
- Live site: https://youcam-claim-guard.vercel.app
- Code (MIT): https://github.com/StuartYo/youcam-claim-guard
- Demo video: https://youtu.be/nD1oqBYZ9rw
Scores come from the YouCam Skin Analysis API by Perfect Corp. The photos are the developer's own, published with his consent. This is an independent hackathon project and is not affiliated with Perfect Corp.
Built With
- model-context-protocol
- nextjs
- node.js
- playwright
- python
- react
- sharp
- typescript
- vercel
- vitest
- youcam-makeup-virtual-try-on-api
- youcam-skin-analysis-api
- zod
Log in or sign up for Devpost to join the conversation.