DEV Community

Cover image for Five things 15 days with the YouCam API taught me that the docs didn't
Ujwal Vanjare
Ujwal Vanjare

Posted on

Five things 15 days with the YouCam API taught me that the docs didn't

I spent the last two weeks building PatternProof, a small app that tries to get a customer and a tailor to agree on a garment before anyone cuts fabric. It uses Perfect Corp's YouCam APIs for the visual side.

This post is not a tutorial. It is the list of things I got wrong, plus a few results I did not expect. If you are about to integrate a generative visual API into anything, some of this will probably save you a day.

The short version of what I built

Virtual try-on exists to answer "should I buy this finished garment?" But in a lot of the world, clothing is made, not bought. India alone has around 12 million custom tailors, 99% of them informal, per the Periodic Labour Force Survey 2023-24.

In that transaction, a customer holds up a phone and says "like this." The tailor interprets, and cuts. Off the rack, a mistake is a return. Made to order, a mistake is destroyed fabric.

So PatternProof renders the garment on the customer's actual body, lets the tailor mark each requirement as can make as shown, can make with adjustment, or not feasible, freezes the whole thing with a SHA-256 digest, and only then lets the customer approve it.

That is the product. Now the interesting part.

1. I refused to write product code until the API proved itself

I gave myself one rule for day one: no application code until a numbered list of tests passed. Boring, and easily the best decision I made.

The one that mattered most I called T3. My assumption was that Clothes VTO would need a clean catalogue-style product image, and that a real customer's screenshot from social media would fail. If that was true, my entire product premise collapsed into "pick from our curated library," which is a much weaker thing.

So I threw three deliberately awful references at it: a garment worn by a different person, an angled and cropped shot, and one in bad light.

All three transferred recognizably. 22.7s, 13.2s, 22.2s.

That single result kept real customer inspiration photos in scope. Had I discovered it on day nine instead of day one, I would have built two days of the wrong thing first.

The takeaway: find the assumption that would break your product, and test only that, before you write anything.

2. A generative API returned byte-identical output

This is the one I still find slightly startling.

For my latency test I sent the same request twice, expecting to compare rough similarity. Instead I got the same file. Not similar. Identical.

Attempt 1: sha256 b53062e7e436dbd9...  141,631 bytes
Attempt 5: sha256 b53062e7e436dbd9...  141,631 bytes
Enter fullscreen mode Exit fullscreen mode

For most projects this is trivia. For mine it mattered a lot, because the entire premise is a frozen agreement. If a customer approves a Cut Card and it re-renders as a slightly different dress on reload, the artifact is worthless.

I built the full content-addressed cache anyway, with a database reservation and lease around every call. Observed determinism is not a documented guarantee, and I am not willing to bet an approval record on behaviour I inferred from five requests. But it was a nice thing to learn.

3. "Success" from the provider is not success for your users

I ran a test with a deliberately dark, half-cropped body photo, expecting a clean error I could catch and surface.

The API returned HTTP success after 31.5 seconds. The image was technically a result and absolutely not something you would ever show a paying customer. Nothing in the response distinguished it from a good render.

That reframed how I thought about the whole integration. Quality judgement could not live at the provider boundary, so it moved into my app, ahead of the spend:

// Reject before we pay, not after.
if (edge < MIN_EDGE) return reject("resolution");
if (grayscaleMean < 70) return reject("lighting");
if (heightRatio < categoryMinimum) return reject("framing");
Enter fullscreen mode Exit fullscreen mode

Rejected uploads cost zero API units, because they never reach the provider. That turned out to be a nice property while filming a demo.

4. Signed URLs work, and I nearly designed around a myth

Before starting I read another dev.to writeup on the same API which stated that Perfect Corp requires publicly accessible URLs for both the user photo and the reference image. Genuinely useful post, and I am glad it existed. But that specific line worried me, because body photos are the most sensitive thing my app touches and I wanted them in a private bucket.

I had already sketched an ugly workaround: copy each image into a short-lived public staging bucket, pass that URL, then purge it.

Before building it, I tested. Supabase expiring signed URLs were accepted without complaint.

So I got to keep the design I wanted. Canonical inputs stay in a private bucket, the provider gets short-lived signed access, and every returned asset is host-allowlisted, size-bounded, hashed, and re-hosted privately before it is shown to anyone.

If you are integrating this API, test that yourself before you build a staging-bucket dance. Mine may not be your configuration, but the assumption is worth thirty seconds of checking.

5. The black video player, and my three wrong theories

This one cost me the most time, and the debugging path is the useful part, so here is all of it including the mistakes.

After approval, the app generates a five-second motion proof with Image-to-Video V2. In production, the player rendered but sat black at 0:00. No duration. No error dialog.

Wrong theory one: the signed URL expired. The video sits inside a collapsed <details>, so the browser only fetches it when you expand. My signed URLs lasted five minutes. Plausible! I raised the TTL to an hour. Still black.

Wrong theory two: the file is broken. So I pulled the actual bytes and looked:

HTTP 200
Content-Type: video/mp4
Length: 1,021,595 bytes
ftyp: isom, codecs avc1 (H.264) + mp4a (AAC)
mvhd: timescale 1000, duration 5063  ->  5.063 seconds
Enter fullscreen mode Exit fullscreen mode

Perfectly valid file. But I did notice this:

mdat (media data) at offset          44
moov (the index)  at offset   1,017,142   <- at the END
Enter fullscreen mode Exit fullscreen mode

Wrong theory three: it is not faststart. True, and worth fixing. With moov after mdat, preload="metadata" cannot resolve duration without pulling the whole file. I switched it to preload="auto", which is correct for a 1 MB clip. Still black.

At this point I had a valid file, correct headers, a reachable URL, and a player that refused to initialise. So I finally looked at the thing I should have checked first: response headers.

img-src   'self' data: blob: https://*.supabase.co   <- images fine
media-src (not declared)                             <- falls back to default-src 'self'
Enter fullscreen mode Exit fullscreen mode

There it was. My Content-Security-Policy declared img-src with the Supabase host but had no media-src, so video inherited default-src 'self' and the browser silently refused to load an MP4 from a different origin. Images kept working through their own directive, which is exactly why it looked like a video problem.

One line:

"media-src 'self' blob: https://*.supabase.co",
Enter fullscreen mode Exit fullscreen mode

And the detail that made it hard: my CSP was applied only when NODE_ENV === "production". Local dev had no CSP at all, so this could never reproduce on my machine.

The lesson I actually took: when one media type loads and another does not, that is a policy problem, not a file problem. Check headers before you start parsing atom offsets. I did it in exactly the wrong order.

The feature I killed on day one

I wanted the customer's own fabric swatch rendered onto the garment. It was the most exciting thing in my original pitch.

Fabric VTO takes a provider-defined template, not an arbitrary upload.

I could have fudged it and shown a plausible fabric direction while implying it was the customer's cloth. Instead I cut the claim entirely. The app now says, on every render:

Visual intent reference. Not a fit, construction, or fabric-drape guarantee.

That disclaimer is my favourite thing in the product. Generative try-on can make intent concrete. It cannot certify measurements, cloth behaviour, or whether a seam is physically constructible. Saying so out loud made everything else more believable.

Which leads to the last thing, and it is not about the API at all.

Three of my own statistics were fake

I did a lot of research with AI assistance. Late on, I went back to verify every number at source rather than trusting a summary. Three did not survive:

  • A "35% of tailoring clients" figure attributed to an industry body. No authoritative source carries it.
  • A "73%" consumer statistic I had recorded as coming from a government ministry. It traces to a fashion brand's blog.
  • A "2 to 3 billion people" figure. I made that one up myself, early, as a plausible-sounding framing, and never went back to check it.

All three had survived multiple review passes. The mis-attributions are the scary ones, because search summaries do not invent numbers so much as invent provenance. A real figure from a blog gets confidently relabelled as coming from a ministry, and it reads as authoritative right up until you click through.

The rule I ended up with, which I would now apply to any AI-assisted work:

If it was not fetched at source, it does not ship.

I applied the same rule to code. No endpoint, parameter, or response shape got written from memory or a search result. Given how the CSP bug went, I am glad about that.

If you are building on this API

  • Test your riskiest assumption on day one, in isolation, before any product code.
  • Read unit costs from the feature-cost endpoint rather than estimating. Mine came back at exactly 2 units per Clothes VTO V3 result, which made real budget enforcement possible instead of guesswork.
  • Provider success is not product success. Put a quality gate in front of the spend.
  • Test signed URLs yourself before designing around public buckets.
  • Declare media-src in your CSP if you are going to serve video from object storage. Please learn this from my afternoon rather than your own.

The app is live and needs no login if you want to poke at it: patternproof-nu.vercel.app. Code is at github.com/usv240/patternproof, including the raw day-one validation record with the timings and failures in it.

Happy to answer anything in the comments, particularly if you have hit different behaviour on the same endpoints. I would genuinely like to know whether the byte-identical result reproduces for anyone else.

Top comments (0)