AI-powered bill splitting made simple
Snap a receipt, let AI extract the items, assign who had what, and get instant fair splits.
- AI-Powered Receipt Scanning - Upload a receipt image and let AI extract items, quantities, and prices automatically using vision models
- Smart Item Assignment - Assign items to participants with simple clicks. Support for quantity-based sharing (e.g., 3 people split 1 appetizer)
- Real-Time Calculations - Instant breakdown of who owes what, including proportional tax allocation
- 30+ Currency Support - Automatic locale detection with support for 30+ currencies worldwide
- Privacy-First Design - No accounts, no database, no persistent data. Everything stays in your browser
- Mobile Optimized - Fully responsive design that works seamlessly on desktop and mobile devices
SplitBill guides you through a simple 5-step workflow:
- Upload - Take a photo or upload an image of your receipt (JPEG, PNG, or WebP up to 10MB)
- Participants - Add the people who were part of the meal
- Review - AI extracts all items, quantities, and prices. Edit if needed
- Assign - Click on items to assign them to each person. Share items by quantity
- Results - See exactly who owes what, with tax distributed proportionally
- Node.js v24 or later
- npm or yarn package manager
- OpenRouter API key (for AI receipt parsing)
- Install dependencies:
npm install- Create a
.envfile in the root directory (or copy the example):
cp .env.example .env- Configure your environment variables:
OPENROUTER_API_KEY=your_openrouter_api_key_here
COMPLETION_MODEL=qwen/qwen3-vl-8b-instruct
UMAMI_WEBSITE_ID=your_umami_website_id_here| Variable | Description | Required |
|---|---|---|
OPENROUTER_API_KEY |
API key from OpenRouter. | Yes |
COMPLETION_MODEL |
Vision model that supports structured output. Default: qwen/qwen3-vl-8b-instruct |
Yes |
UMAMI_WEBSITE_ID |
Website ID for Umami analytics (anonymous, cookie-free). Leave empty to disable. | No |
RATE_LIMIT_PER_MIN |
Per-IP /api/parse requests allowed per minute. Default: 5. |
No |
RATE_LIMIT_PER_HOUR |
Per-IP /api/parse requests allowed per hour. Default: 20. |
No |
TRUST_PROXY |
Set to true only behind a trusted reverse proxy so the limiter reads X-Forwarded-For. Unset for direct exposure — XFF is spoofable. (docker-compose sets this for you.) |
No |
Start the development server on http://localhost:3000:
npm run devSplitBill includes a production-ready Docker setup with nginx reverse proxy.
-
Ensure the
.envfile is configured with your API keys -
Build and start the services:
docker-compose up -d- The app will be available on
http://localhost(port 80)
If you prefer to manage the container manually:
docker build -t splitbill .
docker run -p 3000:3000 \
-e OPENROUTER_API_KEY=your_key \
-e COMPLETION_MODEL=qwen/qwen3-vl-8b-instruct \
--name splitbill \
splitbillFor production deployments with HTTPS:
-
Place your SSL certificates in
./ssl/directory:ssl/fullchain.pem- Your certificate chainssl/privkey.pem- Your private key
-
Ensure
nginx.confis configured for SSL (port 443) -
Start with docker-compose as above
| Category | Technology |
|---|---|
| Framework | Nuxt 4 - Vue 3 Meta Framework |
| Language | TypeScript 5.8 |
| UI Library | @nuxt/ui + Tailwind CSS |
| Validation | Zod |
| AI Provider | OpenRouter API (Vision Models) |
| Analytics | Umami (Cookie-free, optional) |
| Runtime | Node.js v24 (Alpine Linux) |
| Web Server | nginx (production) |
SplitBill is intentionally designed as a single-page app with all state, UI, and logic in src/pages/index.vue. This architecture is ideal for:
- Simple workflow applications
- Reduced complexity for small teams
- Easy state management without external stores
Receipt parsing happens asynchronously via OpenRouter's vision API while users continue through the workflow. The AI extraction doesn't block the user experience.
Tax is distributed fairly based on each participant's share of the total bill:
Participant Tax = (Participant Items Total / Overall Subtotal) × Total Tax
This ensures that someone who ordered a $5 appetizer pays less tax than someone who ordered a $50 steak.
src/
├── pages/
│ └── index.vue # Single-page app (all state & UI)
├── server/
│ └── api/
│ └── parse/
│ └── index.post.ts # AI receipt parsing endpoint
├── composables/
│ └── useAnalytics.ts # Umami analytics integration
└── utils/
└── currency.ts # Locale/currency detection (30+ currencies)
SplitBill is designed with privacy as a core principle:
- No Persistent Data - All data is stored in-memory only. Nothing is saved to databases.
- No User Accounts - No sign-up, login, or personal information required.
- No Tracking Cookies - Umami analytics is completely cookie-free and anonymous.
- Receipt Images - Images are sent to OpenRouter for parsing and not stored afterwards.
- Fonts - Web fonts are loaded from Google Fonts and Fontshare (a third-party request on first paint). This is a deliberate tradeoff vs. the privacy-first positioning; self-hosting is tracked as a future improvement. The production build also fetches Google Fonts at build time (requires network during
nuxt build). - Open Source - Full transparency into how the application works.
npm run dev # Start development server (http://localhost:3000)
npm run build # Production build
npm run generate # Static site generation
npm run preview # Preview production build