work · closeby
CloseBy
AI-assisted hyperlocal marketplace for neighborhood shops
Solo: design, frontend, backend, AI layer · 2026

Problem
Neighborhood shop owners have no time to type a catalog, and buyers can't see what a shop near them has in stock. CloseBy lets owners import stock from a shelf photo or voice list, and lets buyers order and pay online from shops nearby.
In short
- -Two-sided marketplace: geohash radius search, role-based dashboards, live order updates (Supabase Realtime) and in-order chat.
- -Every AI call passes one gateway: auth, rate limit, kill switch, daily spend cap, input cleaning, tool loop, cost logging. Under load, the spend cap blocked 100% of over-cap calls before any model call.
- -AI only drafts (stock imports, restock ideas, chat replies); the owner approves before anything is saved.
- -Load-tested checkout with JMeter: 50 buyers racing for the last 5 units made exactly 5 orders, 0 oversells in 4/4 runs (row locks in one transaction).
- -Razorpay flow holds stock until payment is confirmed and auto-refunds rejected orders.
- -27 eval cases covering Hindi aliases, misspellings and gibberish input.
Architecture
A Next.js app with a single server-side AI gateway. The model never writes to the database directly; it returns Zod-validated drafts that the owner approves.
- user
- client
- guard
- service
- ai
- data
- external
- call
- logical link
- ← scroll →
- 01Owner uploads a shelf photo or voice list
- 02Gateway: auth → rate limit → kill switch → spend cap → input cleaning
- 03Claude tool loop returns a structured draft (validated with Zod)
- 04Owner reviews and approves the draft
- 05Approved items saved to Supabase Postgres; cost logged per call
Decisions
+ One gateway for every model call- Calling the model from each feature directly Rate limits, spend caps and logging live in one place, so a new AI feature can't skip them.
+ Human approval before any write- Letting the model save stock automatically A wrong price or quantity costs a real shop money; a review step is cheap.
+ Integer paise and a seven-state order machine- Float rupees and free-form status strings No rounding errors, and illegal status jumps are rejected at the boundary.
Failure handling
Model returns malformed or partial output
Zod validation fails closed; the owner sees an error, nothing is saved.
Daily AI spend exceeds the cap
Gateway refuses new calls until the next day; manual entry still works.
Prompt injection in uploaded text
Input cleaning plus tool-only output; the model can't trigger writes.
- user
- client
- guard
- service
- ai
- data
- external
- call
- logical link
- ← scroll →
users
userBuyer
search nearby shops, order, pay, track
userShop owner
catalogue, orders, AI drafts to approve
- client
Next.js 15 app
App Router · server actions · Clerk auth · role-gated dashboards
server
guardAI gateway
every model call goes through here
serviceOrder service
7-state machine · integer paise
serviceRealtime
live order updates · in-order chat
providers + data
aiClaude / OpenAI
tool loop · provider switchable
dataSupabase Postgres
catalogue · orders · cost logs · RLS
externalRazorpay
checkout · webhooks · refunds
- user
Owner uploads a shelf photo or voice list
- guard
Session + role check
not an owner → rejected
- guard
Rate limit → per-helper kill switch → daily spend cap
cap reached → AI off until tomorrow, manual entry still works
- guard
Input cleaning + size limits
user text fenced against prompt injection
- ai
Tool loop
model reads the catalogue through tools; can't write
- guard
Zod validation
malformed output → error shown, nothing saved
- client
Draft shown to the owner
edit, approve or discard
on approve
dataApproved items saved
dataCost logged for the run
- ● PLACED
- ACCEPTED
- PREPARING
- READY
- COMPLETED
CANCELLED
unpaid orders cancelled after 15 minutes; stock released
REJECTED
owner rejects; paid orders refunded automatically
Calling the model from each feature directly
One gateway for every model call
Rate limits, spend caps and logging live in one place, so a new AI feature can't skip them.
Letting the model save stock automatically
Human approval before any write
A wrong price or quantity costs a real shop money; a review step is cheap.
Float rupees and free-form status strings
Integer paise and a seven-state order machine
No rounding errors, and illegal status jumps are rejected at the boundary.
Model returns malformed or partial output
Zod validation fails closed; the owner sees an error, nothing is saved.
Daily AI spend exceeds the cap
Gateway refuses new calls until the next day; manual entry still works.
Prompt injection in uploaded text
Input cleaning plus tool-only output; the model can't trigger writes.
Numbers
0
Oversells, last-stock race
50 buyers · 5 units · 4/4 runs
800
Concurrent orders, 0 errors
~160 orders/s
153 ms
Search p95 at 50 users
27
Eval cases
Load tests
JMeter 5.6.3 against a production build (1 Node process, 5-connection DB pool) with local Postgres, mock Claude and stubbed Clerk users.
Last-stock race
50 buyers at the same instant, 5 units left (×4 runs)
Exactly 5 orders, 45 clean rejections, stock 0: 0 oversells in 4/4 runs
Checkout load
100 buyers × 8 COD orders
800/800 OK, 0 stock mismatches, ~160 orders/s, p95 697 ms
Nearby search
50 users, 30 s
10,529 requests, 0 errors, ~350 req/s, p95 153 ms
Nearby search
200 users, 60 s
21,690 requests, 0 errors, ~360 req/s, p95 620 ms
AI rate limit
1 user, 30 rapid calls
15 allowed, 15 rejected with 429
AI spend cap
User over the daily cap, 20 calls
20/20 blocked, 0 model calls made
AI gateway load
50 users × 5 calls
250/250 OK
Forged Razorpay webhooks
100 bad signatures
100/100 rejected with 401
Next
- ○ Grow the eval set to ~100 hard cases
- ○ Record a 90-second demo video
- ○ Stock substitution suggestions