KindredPics: Option A — paid-scan minimum batch of 50 photos¶
Decision¶
The paid face-scan path enforces a hard minimum of 50 paid photos per scan request. The free-tier path (first 100 photos/account, lifetime) is unaffected — free-quota users never see the gate. The pre-applied promo-code path (KPFAMILY etc., 100%-off coupons) bypasses the gate because $0 sessions sidestep the Stripe floor entirely.
Why¶
Stripe Checkout rejects sessions under $0.50 USD at creation time, before any user-typed promo code at the hosted page can apply. KP's $0.01-per-photo pricing meant every paid batch <50 photos returned amount_too_small (502'd in prod for the entire 2026-05-31 PM Stripe-gateway test session until the workaround landed).
Three options were on the table: - (a) Min-batch UI gate ≥50 — chosen - (b) Pack pricing ($0.50 for 50-photo bundles) — rejected: breaks per-unit clarity, complicates partial free/paid splits - © Prepaid credits — rejected: heaviest implementation, adds a wallet abstraction for a problem we don't have yet
Option A preserves penny-per-photo unit pricing (simplest mental model) and surfaces the constraint at the in-app friction point only, not in marketing copy.
What shipped¶
functions/api/photos/scan-request.js— newMIN_PAID_PHOTOS = 50constant. After promo lookup, before Stripe Checkout creation: ifpaidNow > 0 && paidNow < 50 && !preAppliedCouponId, DELETE orphanscan_requestsrow + return 400{error: "min_batch_under_50", paid_count, free_tier_count, free_remaining, min_paid_photos, hint}.src/app.htmlBIPA notice — added "paid scans run in batches of 50+ photos at a time" clause.src/app.htmlkpStartScanFlow— handles 400 with actionable alert pointing to KPFAMILY DM fallback.
What did NOT change¶
src/pricing.html— public page hides all paid pricing ("free during early access"). Surfacing a 50-photo minimum line there would prematurely expose paid pricing that's gated behind an "we'll email you first" promise. Revisit when paid plans get an announced public surface.- Gold CTA copy in LibraryView — still reads "Find people automatically · 100 free" even for users past free quota. Plumbing
free_remaininginto the React component is invasive vs the friction value; tracked as Phase 3 polish. Zero real users past quota currently, so cosmetic only.
Bypass paths¶
- KPFAMILY (and any future 100%-off promo) — pre-applied via
discounts[0][coupon]in the request body skips the gate - Free quota — users with
free_scans_quota - free_scans_used > 0never hit the paid path for at least the free portion; gate only fires when there's a non-zero paid portion
Open¶
- Cosmetic CTA copy update for post-free-quota users (Phase 3)
- Marketing surface for paid plans (TBD — page intentionally hedged for now)
- Not yet deployed;
bash scripts/deploy.shpending Devin's go-ahead (Lindsey smoke-test still mid-flight)