AwaazOS — Bol. Bill. Becho.
Orders, UPI billing and receipts for India's home kitchens that sell on Instagram and WhatsApp. Post a batch with a cut-off or an everyday menu → buyers order from your link in bio or on WhatsApp → paid by UPI → cook list and delivery list on the day → receipt on WhatsApp. Voice POS for counter sales. 30 days free.
Status: Production-deployed, with design-partner vendors in Hyderabad. Live counts are on the admin-only
/admin/metricspage.
🎬 60-Second Demo
Voice command in Hindi → cart fills in real time → UPI QR → WhatsApp receipt. Sixty seconds, end-to-end, on a real phone, in a real chai stall.
The recorded video is not published yet. The original April 2026 script is archived (and partly stale) at
docs/archive/DEMO_VIDEO.md.Live walkthroughs (Zoom / investor rooms):
docs/DEMO_GUIDE.md.
🌐 Live demo
| Surface | URL |
|---|---|
| Production app | https://awaazos.vercel.app |
| Pricing | https://awaazos.vercel.app/pricing |
| Sign up | https://awaazos.vercel.app/sign-up |
| GitHub | https://github.com/gengirish/awaazos |
| 60s video demo | not published yet |
| Live demo script | docs/DEMO_GUIDE.md (Zoom / investors) |
60-second test: sign up → onboard a stall → add 3 menu items → open
/poson your phone → tap the orange mic → say "do samosa aur ek chai" → see the cart fill → scan the UPI QR → billAWZ-YYYYMMDD-0001is generated atomically → check/analytics.
🎯 Who it's for
Instagram-based cloud kitchens: home bakers, meal-box kitchens, snack and sweet makers who find buyers on Instagram and sell direct, not through Swiggy or Zomato. Today their orders live in DMs, payments are UPI screenshots, and the cook list is a notebook. Full profile, fit and gaps: docs/ICP.md.
What they use: the storefront /s/[slug] as their link in bio, Drops (a batch with a cut-off
and capped stock), UPI / pay-on-delivery / their own Razorpay, the orders board and prep sheet,
Web Push alerts, and a ready-made Instagram story image for each drop.
The sections below describe the original counter-POS wedge (street stalls and kiranas). That product still ships and still works; it is no longer who we build for first.
🌶️ The problem (original counter-POS wedge)
India has 63 million MSMEs and ~25 million micro-merchants — chai walas, dosa carts, kirana shops, samosa stalls, paan shops. They:
- Take orders verbally in Hindi / Telugu / Kannada / Tamil / Marathi / Bengali (rarely in English)
- Hand-write bills on paper or skip billing entirely (lose 15-20% of revenue to inventory leakage and disputes)
- Accept UPI but can't tie payments to orders
- Have no idea which item sold most, what their busiest hour is, or how much they actually made today
- Can't afford ₹2,000-5,000/mo POS systems built for English-speaking, literate restaurant managers
The gap: existing POS software (Petpooja, Zomato Hub, Square clones) assumes a manager-class user who can type, navigate menus, and trust an English UI. That cuts out 90% of India's actual point-of-sale.
💡 The solution
A POS where the only interface is your voice and your eyes.
- Vendor speaks the order in Hindi or English → the browser's Web Speech API transcribes it (a server-side STT cascade exists as an opt-in fallback; see Tech stack below)
- Claude Haiku 4.5 parses the transcript against this vendor's exact menu (with prompt caching) and emits structured items via tool-use
- Cart fills, total computes in paise (no float math, ever)
- Vendor taps Collect Payment → big UPI QR code → customer scans with GPay / PhonePe / Paytm
- Mark as paid → atomic bill number
AWZ-20260423-0001→ DB transaction commits → vendor taps Send on WhatsApp and the receipt opens pre-filled in their own WhatsApp (wa.melink) - Owner sees real-time dashboards (revenue trend, top sellers, hourly heatmap, voice adoption %)
All of this on a phone, in <8 seconds per transaction. Hindi + English at MVP. More on roadmap once 50 vendors are active.
✨ Features
Built and shipped
- 🎙️ Voice-first POS — 80×80 mic button, 6 visual states (idle / listening / transcribing / parsing / success / error), distinguishable beyond color, full
aria-livefor screen readers - 🌍 STT routing — Web Speech in the browser for English & Hindi. A server cascade (
/api/ai/transcribe, STARTER+) is wired with per-provider circuit breakers: OpenAI → Groq → self-hosted Whisper for en/hi, Sarvam Saaras v3 → Groq → Whisper for Telugu / Kannada / Tamil. te/kn/ta are dormant: the code and golden fixtures are kept, but the language picker offers only English and Hindi - 🧠 Two-tier AI parsing — Claude Haiku 4.5 primary with ephemeral prompt caching on the menu, escalates to Sonnet 4.5 on empty or low-confidence results (
shouldEscalate()) or a Haiku error - 💰 Integer-paise math everywhere — no
parseFloat, notoFixed(single exception: UPI deep-linkam=per spec) - 🔢 Atomic bill numbering —
AWZ-YYYYMMDD-NNNNviaBillSequence.upsertinsideprisma.$transaction, race-safe across concurrent sales - 📲 WhatsApp receipts — the vendor sends the bill from their own WhatsApp via a pre-filled
wa.melink (GET /api/receipts/message); no Business API, no template, free on every plan. Customer phone is PII-protected (never logged, only last-4 in CSV exports). An older Gupshup + BullMQ path is still in the code but unused by the UI - 💳 UPI deep links + QR — vendor's own UPI ID, no MDR fees, scannable from any UPI app
- 📊 Analytics dashboard — Recharts revenue trend, hourly heatmap, top sellers, voice adoption %, payment method breakdown — every chart has a
<details>data table for accessibility - 🏢 Multi-tenant —
vendorIdscoping enforced on every query, two-layer RBAC (platformUserRole+ per-tenantStaffRole) - 💸 Trial + Razorpay subscriptions — 30-day free trial with Starter limits, then Starter at ₹299/month or ₹2,990/year (+ GST), paid in place with Razorpay Checkout. Trial, paid and locked states are derived on every read (
billingAccess()), with no timers; a locked store keeps read access to its records. A super admin can extend a store's free access by date (/admin/vendors) - 🖼️ Store banner & logo — the owner uploads a cover photo and logo for
/s/[slug]in Settings → Online store; photos are shrunk on the phone before upload - 🛍️ Public storefront + Drops — opt-in
/s/[slug]page where buyers order from the everyday menu or from a pre-order "drop" (cut-off, capped stock, prep sheet). Payment by UPI + the buyer's "I've paid" claim (the seller confirms), pay on delivery, or the vendor's own Razorpay keys. Optional Instagram and WhatsApp links in the store header - 📋 One orders board —
/ordersholds menu orders and every drop's orders, with a source picker, search, pickup/delivery filter, bulk confirm / mark paid / deliver, and Undo after handover - 🏷️ Offers — quantity plan discounts ("4+ quiches incl. 1 paneer → 10% off") on the storefront, drops and POS; one offer per order, priced by the server (
lib/offers.ts, Settings → Offers) - 🗣️ Voice drop and menu builders — say a batch plan or a dish list; Claude drafts the drop form or new menu rows for the seller to confirm (
/api/ai/parse-drop,/api/ai/parse-menu) - 🧾 Receipts — one receipt layout rendered as WhatsApp text, a printable 58 mm / 80 mm / A4 page with optional auto-print, and a public customer page
/r/[token]; GSTIN printed when set - 📴 Offline counter sales — the POS keeps selling without a connection: sales queue in IndexedDB and sync when the phone is back online, never billed twice (
clientRef) - 📸 Instagram story image — one tap on a drop makes a 1080×1920 PNG (items, prices, cut-off, order link) inside Instagram's safe zones, and copies the storefront link for the Link sticker (
GET /api/drops/[id]/story-image) - 🔔 Web Push alerts — new storefront orders and payment claims push to the vendor's opted-in devices (
lib/push.ts,public/sw.js); an open app also says "New order received from - ✅ Setup checklist — the steps to start taking orders follow the seller to
/orders,/dropsand/pos; in-app guide at/help - 🇮🇳 India-first defaults — IST timezone, DD/MM/YYYY dates,
+9110-digit phone validation,Intl.NumberFormat('en-IN', { currency: 'INR' }),libphonenumber-js - 🔐 Auth.js v5 (NextAuth) — Edge-split config, JWT sessions, Credentials provider with bcrypt, single
prisma.$transactionfor sign-up - 🎨 Mobile-first UI — Tailwind + shadcn/ui, saffron AwaazOS palette (primary tokens at 4.5:1 with white text), Baloo 2 headings + Nunito body, shared
EmptyState/Notice/ResponsiveDialogprimitives
Roadmap
Driven by docs/ICP.md "Plan, in order": validate with 5–10 kitchens running real drops, checkout inside Instagram's in-app browser, measuring Instagram demand, a store-level story image, and delivery hand-off from the prep sheet.
Also planned, not shipped: automated accessibility audits in CI (@axe-core/playwright is not installed today — a11y is hand-checked against the aria-live, contrast, and chart-data-table work listed above).
🏗️ Tech stack
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js 15 App Router + React 19 + TypeScript strict | RSC for server-side data fetch, no waterfall to API |
| UI | Tailwind CSS 4 + shadcn/ui (slate) | Fast iteration, accessible primitives, mobile-first |
| Charts | Recharts ^2.15 | SSR-friendly, accessible (data tables sibling) |
| Auth | Auth.js v5 (NextAuth) + @auth/prisma-adapter + bcryptjs |
No vendor lock-in, edge-safe middleware, JWT sessions |
| Database | Neon Serverless PostgreSQL | Branchable, scales to zero, Indian-region availability |
| ORM | Prisma | Type-safe, $transaction for atomicity, upsert for idempotency |
| AI / Voice | Anthropic Claude Haiku 4.5 (primary) + Sonnet 4.5 (fallback), tool-use, prompt caching | Best Indic instruction-following at lowest cost |
| STT | Web Speech API (en/hi, in-browser) + server cascade: OpenAI gpt-4o-mini-transcribe, Groq whisper-large-v3-turbo, Sarvam Saaras v3 (dormant te/kn/ta), self-hosted Whisper on Fly |
Web Speech is free and needs no server call from us; the cascade covers the opt-in server path, each hop behind its own circuit breaker |
| Background jobs | BullMQ + Redis (Fly.io workers) | Voice telemetry, retention runs, retries with backoff |
| Serverless KV | @upstash/redis HTTP on Vercel | Rate limits, idempotency keys, AI usage caps; no TCP from serverless |
| Payments | Razorpay subscriptions (platform) · UPI deep link + QR (POS) · vendor's own Razorpay keys (storefront) | India-first; storefront money goes to the vendor, never through the platform |
| Messaging | wa.me links from the vendor's own WhatsApp · Web Push (web-push, VAPID) · AgentMail (password-reset and buyer magic-link email) |
No WhatsApp Business API or templates needed |
| Hosting | Vercel (Next.js) + Fly.io BOM region (workers) | Edge for users, BOM for low-latency to Indian compute |
| Storage | Vercel Blob | Menu item photos, store banner and logo |
| Testing | Vitest (unit) + Playwright (E2E — chromium + Pixel 7 mobile, en-IN locale, Asia/Kolkata timezone) |
Fast unit feedback, real-browser E2E on the exact locale and timezone vendors run in |
🚀 Quickstart (local dev)
# 1. Clone + install
git clone https://github.com/gengirish/awaazos.git
cd awaazos
npm install
# 2. Copy env template, fill in your secrets
cp .env.local.example .env.local
# Required to boot: DATABASE_URL, AUTH_SECRET (set AUTH_URL too)
# Required for voice parsing: ANTHROPIC_API_KEY
# Optional server STT: OPENAI_API_KEY, GROQ_API_KEY, WHISPER_STT_URL + WHISPER_STT_SECRET
# (SARVAM_API_KEY only matters for the dormant te/kn/ta path)
# Optional: RAZORPAY_* (incl. RAZORPAY_PLAN_STARTER_MONTHLY / _YEARLY), BLOB_READ_WRITE_TOKEN,
# VAPID keys (Web Push), AGENTMAIL_*, SENTRY_*
# Redis: UPSTASH_REDIS_REST_* (HTTP) and REDIS_URL (TCP, BullMQ) — see "Two Redis surfaces" in CLAUDE.md
# Every variable is documented in .env.local.example
# 3. Push Prisma schema to your Neon DB (no migration files, no seed script)
npx prisma db push
# 4. Run dev server
npm run dev
# → http://localhost:3000
# 5. (Optional) Run BullMQ workers locally (voice telemetry, retention)
docker run -d --name awaazos-redis -p 6379:6379 redis:7-alpine
npm run worker # or: npx tsx workers/index.ts
Provision a Neon database
- Sign up at https://neon.tech
- Create project
awaazos-dbin theaws-us-east-1region (closest to where Vercel deploys serverless functions; switch if Vercel deploys to Mumbai) - Copy the pooled connection string →
DATABASE_URL - Copy the direct connection string (same URL minus
-poolerfrom the host) →DIRECT_URL
Provision Anthropic
- Sign up at https://console.anthropic.com
- Create an API key, paste into
ANTHROPIC_API_KEY - Default models are
claude-haiku-4-5(primary) andclaude-sonnet-4-5(fallback) — override withANTHROPIC_PRIMARY_MODEL/ANTHROPIC_FALLBACK_MODELif needed
🧪 Quality & testing
npm run typecheck # tsc --noEmit
npm run lint # eslint . (ESLint 9 flat config)
npm test # vitest run
npm run build # next build
npm run e2e # playwright (uses dev server) — chromium + mobile
Vitest unit + Playwright E2E (chromium + Pixel 7 mobile, en-IN locale, IST timezone). See ARCHITECTURE.md for the full testing stack and what each suite guards.
The E2E suite writes real rows (it registers vendors and rings up sales). Never run it against the production database or deployment; use a throwaway Postgres as shown in
CLAUDE.md.
☁️ Production deployment
The app is deployed across these services:
| Service | Host | What runs there |
|---|---|---|
| Web | Vercel (awaazos.vercel.app) |
Next.js App Router, all /api/* routes, marketing + vendor pages |
| Workers | Fly.io awaazos-workers (BOM region) |
BullMQ workers for voice-parse-log, send-receipt (dormant Gupshup path) and retention. The Fly app does not currently exist; see TODO.md 1.1 |
| Whisper STT | Fly.io (services/whisper-stt) |
Optional self-hosted last-resort ASR |
| Redis (HTTP) | Upstash REST (UPSTASH_REDIS_* on Vercel) |
Rate limiting, idempotency, circuit breakers, monthly AI caps, /api/ready Redis probe |
| Redis (TCP) | REDIS_URL, a public rediss:// endpoint |
BullMQ: Vercel routes enqueue, Fly workers consume |
| Database | Neon Serverless Postgres (us-east-1) | All app data, prisma.$transaction boundaries |
The two Redis surfaces are not interchangeable; the canonical explanation is in CLAUDE.md.
Deploy from scratch
Vercel deploys through its GitHub integration. vercel.json skips every non-production build, so preview deployments are off and a push to main is a production deploy.
# 1. Vercel (frontend + API): connect the GitHub repo in the Vercel dashboard, then set env
vercel link
vercel env add DATABASE_URL production # paste Neon pooled URL
vercel env add AUTH_SECRET production # openssl rand -base64 32
vercel env add AUTH_TRUST_HOST production # value: true
vercel env add AUTH_URL production # the public production origin
vercel env add ANTHROPIC_API_KEY production
# Upstash REST (Vercel serverless — rate limits, idempotency, /api/ready)
vercel env add UPSTASH_REDIS_REST_URL production # https://....upstash.io
vercel env add UPSTASH_REDIS_REST_TOKEN production
# REDIS_URL: public TCP endpoint. Without it, BullMQ enqueues fail (voice telemetry is dropped).
vercel env add REDIS_URL production
# ... (add OPENAI_* / GROQ_* / WHISPER_*, RAZORPAY_*, VAPID keys, AGENTMAIL_*, BLOB_READ_WRITE_TOKEN, SENTRY_* as needed)
git push origin main # builds and deploys production
# 2. Fly.io workers
flyctl launch --name awaazos-workers --region bom --no-deploy
# Provision Redis with a TCP URL workers can hold open (Fly Redis, or Upstash rediss://)
flyctl redis create --name awaazos-redis --org personal --region bom \
--no-replicas --enable-eviction --enable-prodpack=false
# copy the printed URL — private Fly *.internal URLs do NOT work from Vercel
# A worker holds a TCP connection 24/7; on a pay-per-command Redis plan that bills even at zero traffic.
# DATABASE_URL + AUTH_SECRET are boot-required by lib/env.ts. GUPSHUP_* only feed the dormant
# receipt worker; the workers never call Anthropic.
flyctl secrets set --app awaazos-workers \
REDIS_URL="rediss://..." \
DATABASE_URL="postgresql://..." \
AUTH_SECRET="..." \
GUPSHUP_API_KEY="..." \
GUPSHUP_APP_NAME="..." \
GUPSHUP_SOURCE_NUMBER="..."
flyctl deploy --app awaazos-workers --remote-only --ha=false
The optional Whisper ASR service has its own Fly app and instructions in services/whisper-stt/README.md.
Per-deploy workflow we follow
For every meaningful change:
npm test && npm run typecheck && npm run lint && npm run build— must all pass- Playwright against a throwaway Postgres (see
CLAUDE.md), never against production git commit -m "feat(scope): ..."— conventional commitsgit push origin main— the GitHub integration builds and deploys production; CI runs lint, typecheck, unit, build, the bundle-size budget, gitleaks andnpm audit(the Playwright job is switched off on CI, so step 2 is the only E2E run)
See ARCHITECTURE.md for system diagrams, docs/OBSERVABILITY.md for production monitoring/alerting setup, docs/README.md for the index of ops docs and production controls, docs/DEMO_GUIDE.md for live demo walkthroughs, and PITCH.md for the YC pitch.
🔐 Security
- Secrets — never committed;
.env.localis gitignored. Real secrets live only in Vercel env + Fly secrets. - PII boundaries — customer phone numbers are stored in Neon for re-send capability but never sent to LLMs or STT providers, never logged in plaintext (only last-4 in CSV exports / log lines).
- Webhook auth — Razorpay webhooks (platform and per-vendor storefront) verified via HMAC-SHA256 with
crypto.timingSafeEqual. - HTTPS only —
Strict-Transport-Security(2 years,includeSubDomains,preload) is set innext.config.mjsalongside the CSP;AUTH_TRUST_HOST=truelets Vercel's proxy work for Auth.js. - Tenant isolation — every Prisma
whereclause includesvendorId. HelperrequireVendorRole(role)returns the resolved vendor and throws on cross-tenant access.