Skip to content

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 — new MIN_PAID_PHOTOS = 50 constant. After promo lookup, before Stripe Checkout creation: if paidNow > 0 && paidNow < 50 && !preAppliedCouponId, DELETE orphan scan_requests row + return 400 {error: "min_batch_under_50", paid_count, free_tier_count, free_remaining, min_paid_photos, hint}.
  • src/app.html BIPA notice — added "paid scans run in batches of 50+ photos at a time" clause.
  • src/app.html kpStartScanFlow — 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_remaining into 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 > 0 never 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.sh pending Devin's go-ahead (Lindsey smoke-test still mid-flight)