Free and Open Source Alternative to Splitwise. Share expenses with your friends and family.
See the codeSpliit Cloud is a community-maintained fork of Spliit: a free, open-source expense splitting app for groups, trips, roommates, friends, and shared costs.
No email or social login required: start with an anonymous account in a few clicks — no email address, no Google/GitHub/OIDC — and keep it recoverable with a recovery link or a passkey.
It keeps the simplicity of the original Spliit while fixing its biggest weakness: groups that lived only in the browser and never synced properly across devices. Cloud accounts give you reliable group syncing, stronger tests, and a more maintainable stack — without forcing everyone onto email.
Public instance: https://spliit.cloud — no email or social account needed. Choose the anonymous option at sign-up (a display name plus a recovery link or passkey) and you get synced groups on all your devices.
Live uptime & incident status at https://status.spliit.cloud
You can also self-host your own instance. See Self-hosting overview, Run locally, and Run in a container.
[!IMPORTANT] The public instance is provided as a community-hosted service. If you need full control over data, uptime, backups, or privacy, self-hosting is recommended.

Spliit Cloud is a community-maintained fork of Spliit, the open-source expense splitter originally created by Sebastien Castiel. It aims to keep the lightweight, no-frills experience that made Spliit popular while evolving the product toward reliable multi-device sync and a stack that is easier to operate and self-host.
People loved that original Spliit needed no account — and hated that their groups lived only in one browser, vanished when site data was cleared, and never synced to their phone. Spliit Cloud is the middle ground: an anonymous account takes a few clicks and asks for no email or social identity, yet your groups follow you across devices and every edit is attributable to a real member. Add email, social login, or a passkey later if you want — or never.
The public instance lives at spliit.cloud: the web app runs on Cloudflare Pages, the API runs on a Hetzner VPS via Dokploy with PostgreSQL on the same VPS, database backups are written to a dedicated Cloudflare R2 bucket, and asset uploads are stored in a separate Cloudflare R2 bucket.
Spliit Cloud exists because I liked Spliit and wanted to keep using it with my friends.
The original Spliit project, created by Sebastien Castiel, is a clean and useful open-source alternative to Splitwise. I first looked at contributing improvements upstream, but after submitting fixes and reviewing existing issues and pull requests at the time, the project appeared to have slowed down.
Since then upstream has become active again (see Relationship to Spliit): 1.20–1.26 shipped global balances, expanded stats, PWA offline assets, monthly category visuals, and security hardening. The stacks and data models have diverged too far to merge back, and hundreds of people now depend on spliit.cloud — so this fork continues as its own line while porting upstream prior art where it fits.
This fork is meant to continue that work openly, with proper credit to the original author and project.
The main things I wanted to improve are:
Spliit Cloud ships only account-bound groups. I explored supporting both local and synced groups in spliit-app/spliit#495, but the dual model became hard to implement and hard to explain. Binding groups to accounts keeps the mental model simple, the data secure, and every action attributable to a member. You do not have to use email or social login: create an anonymous account in a few clicks — just a display name, no email address, no Google/GitHub/OIDC — and keep it with a recovery link or a passkey. You get all the benefits of synced groups with the same simplicity people loved about account-free Spliit, while the group still sees a real member with authorization instead of a shared URL anyone can edit. See the FAQ for the full reasoning.
Spliit Cloud is not affiliated with the original Spliit project unless stated otherwise.
Spliit Cloud is a community fork of spliit-app/spliit.
Credit for the original idea, design, and foundation belongs to the original Spliit project and its creator, Sebastien Castiel.
This fork keeps the project open-source and aims to continue development in a direction focused on accounts, syncing, maintainability, and self-hosting.
Upstream is actively maintained again: since August 2026 it has shipped 1.20–1.26 with real improvements (global balances, expanded group stats with date ranges and drill-downs, monthly category visuals, PWA service worker with offline assets, settle-up in a non-group currency, configurable OpenAI endpoint/models, security headers/CSP, and even-split previews). Switching back is no longer practical — hundreds of people depend on spliit.cloud, groups here are account-bound rather than URL-identified (with anonymous accounts preserving the no-email simplicity), and the stacks have diverged (Vite + React SPA with Hono + tRPC here, Next.js upstream). Where upstream ships useful prior art, this fork ports and credits it instead of pretending the gap is still one-sided; the feature table below reflects that parity honestly.
Spliit Cloud may be useful if you want:
| Feature | Why it matters | Spliit Cloud | Original Spliit |
|---|---|---|---|
| Core Spliit features | Groups, categories, receipts and drag-and-drop attachments, balances, settlements, advanced splits, PWA support, and no ads. Upstream added an offline-asset service worker with update prompt in 1.23.0. | ✅ | ✅ |
| Accounts, friends, and synced groups | Sign in with email, passkeys, optional OIDC/OAuth, or an anonymous account with no email or social login. Profile avatars, 1-on-1 friend ledgers, password set/change, email change, last-used sign-in badge, and groups that follow you across devices—with every action attributable to an account. | ✅ | ❌ |
| Passkeys and account security | Passwordless sign-in for every account type (including guests), backup sign-in choice at signup, per-instance ENABLE_PASSKEY_AUTH kill switch, invite-only SIGNUP_MODE for private instances. | ✅ | ❌ |
| Import and export | Import Splitwise CSV, bank-statement CSVs (mapping wizard, duplicate detection), Cospend projects, original Spliit, and Spliit Cloud bundles; export CSV, printable PDF reports, and full group or account ZIP archives including documents. Upstream added notes/history to JSON export in 1.21.0. | ✅ | ❓ (group JSON export only; Splitwise #483 / Tricount #526 still open) |
| Reliable currency handling | Multi-currency expenses with server-side conversion, a conversion widget, exact group-currency amount override (fees included, implied rate shown), and cryptocurrency support. Upstream fixed the Frankfurter endpoint and minor-units bugs and added settle-in-other-currency in 1.20–1.23. | ✅ | ❓ (fixed basics, no server conversion widget or crypto) |
| Multiple payers and itemized expenses | Track several payers, line items, tax, tip, proportional-to-items remainder splits, and precise per-person shares in one expense. | ✅ | ❌ |
| Durable recurring expenses | Create real recurrence series with intervals, date/count/indefinite endings, catch-up, retries, history, previews, navigation, and explicit stop/edit/delete controls—not read-time side effects. Upstream recurrence got E2E and date fixes but no durable series. | ✅ | ❓ (basic recurrence only) |
| Activity, comments, and discovery | Per-expense change history and comments, For-you/All involving timeline, manual date entry, archived-group search, plus preview, filtering, sorting, and search across groups—without opening the edit form. | ✅ | ❓ |
| Sharing and group identity | Multi-use 15-minute Nearby QR invites with Scan-to-join, group emoji and colors, public view-only links, and member management. Upstream rebased QR sharing in 1.22.0. | ✅ | ❓ (QR sharing only) |
| Outbound webhooks | Subscribe any HTTPS endpoint to signed expense.created/updated/deleted events with retries, delivery history, redelivery, involved-only filtering, and per-endpoint secrets. | ✅ | ❌ |
| Email and push notifications | Deliver group and expense updates through email or push, with per-user preferences for which categories and channels are enabled. SSO-only instances (ENABLE_EMAIL_AUTH=false) run without SMTP. | ✅ | ❌ |
| Stats, charts, and budgets | Per-group spending charts, monthly category visuals, cross-group roll-up, plus weekly, monthly, yearly, or custom budgets by category and participant, with over-budget notifications. Upstream added global balance, expanded stats, drill-downs, and monthly visuals v1 in 1.23–1.25. | ✅ | ❓ (stats without budgets) |
| Balances, settlements, and subgroups | Switch between balance views, settle faster, combine compatible payments, or settle as subgroup units such as couples. Upstream added by-amount remainder display and even-split previews in 1.25–1.26. | ✅ | ❓ (basic balances only) |
| Full localization | Supported languages have complete and a maintained i18n validation workflow, with sparse overlays and fallback chains (e.g. en-GB, pt-BR). Upstream uses Weblate with steady translation activity. | ✅ | ❓ (Weblate, no audited-complete workflow) |
| OpenAPI, Scalar, MCP, and OAuth | Explore the published API interactively, consume the OpenAPI spec, create expenses from ChatGPT and Claude through the optional MCP assistant, or connect scripts and agents via delegated OAuth 2.1 with per-resource scopes and connected-app controls. | ✅ | ❌ |
| Versioned releases and images | Every release publishes immutable :vX.Y.Z GHCR images for all services with :latest tracking the newest stable release; SPLIIT_TAG pins upgrades and the public instance deploys per release. Upstream ships standalone/runtime images with feature flags. | ✅ | ❓ (images, no versioned release train) |
| Group archive & delete | Archive groups to make them view-only, or permanently delete them when no longer needed. | ✅ | ❌ |
| AI-assisted expense workflows | Categorize from the title via local dictionary, group history, then one AI engine (LLM or System One Jev with AI_CATEGORY_ENGINE), uncertain guesses as one-tap chips, per-user opt-out; scan receipts and describe expenses by voice; extracted details stay up for review before saving. | ✅ | ❓ (configurable OpenAI endpoint/models since 1.23.0) |
| Expense amount calculator | A calculator widget in the expense amount field for quick arithmetic while entering expenses. | ✅ | ❌ |
| Responsive mobile experience | Mobile-specific layouts and interaction patterns improve the experience beyond simply narrowing the desktop UI. Upstream added mobile tab icons in 1.23.0. | ✅ | ❓ (mostly a narrow desktop app) |
| Active maintenance | New features, fixes, and self-hosting improvements continue to move forward — see releases. Upstream is active again too (1.20–1.26 in Aug–Sep 2026 with stats, PWA, security, and perf work). | ✅ | ✅ |
The detailed roadmap is the source of truth. Recent work shipped imports (bank CSV, Cospend), delegated OAuth for agents, Nearby QR invites, outbound webhooks, group emoji/colors, passkeys, and System One categorization. Next up are privacy/trust features (end-to-end encryption and offline support), expanded integrations, and self-hosting polish.
Spliit Cloud is still evolving. Current limitations may include:
Please open an issue if you hit a bug or if a missing feature blocks your usage.
Spliit Cloud stores expense data needed to make the app work, including groups, participants, expenses, balances, and uploaded expense documents if that feature is enabled.
The public instance is hosted as follows:
For users who want full control over data and infrastructure, self-hosting is supported. See PRIVACY.md for the detailed data-handling notes and Self-hosting overview for running your own instance.
If you discover a security issue, please follow the responsible disclosure process in SECURITY.md instead of opening a public issue.
The supported Docker setup runs the web app, API, background worker, migrations, and PostgreSQL as one Compose project. The web container is the only public entry point: it serves the SPA and proxies API/auth requests over the private Docker network. Point any HTTPS reverse proxy at the web port.
By default, SMTP is required for sign-in links, email verification, recovery,
and invitations. Set ENABLE_EMAIL_AUTH=false for an SSO-only instance: the email
form is hidden, email endpoints are rejected, and SMTP becomes optional — see
docs/deployment.md. S3-compatible document storage, AI
features, OAuth providers, Web Push, and the MCP assistant are optional.
Prerequisites: Bun, Docker, Node 24+, and portless
(npm install -g portless, then portless trust once so browsers accept the
local HTTPS certificate).
Clone the repository (or your fork if you intend to contribute).
Run bun install to install dependencies.
Copy .env.example to .env (cp .env.example .env).
Start local services, run Prisma operations, and start the app servers:
bun dev:up # postgres, maxio, maildev via compose.dev.yaml
bun prisma-migrate
sudo portless proxy start -p 443 --https # once per boot; serves the https names below
bun dev # web, api, worker admin via portless
Pre-starting the proxy matters: otherwise each service auto-starts the
default plain-HTTP proxy and the https origins won't resolve. Verify with
portless list. People without portless installed can run the underlying
commands directly (dev:app / preview:app / dev:relay:app scripts).
| Service | URL (portless) | Bypass (PORTLESS=0) |
|---|---|---|
| Web | https://spliit.localhost | http://localhost:3000 |
| Web preview | https://preview.spliit.localhost | http://localhost:4173 |
| API | https://api.spliit.localhost | http://localhost:3001 |
| Worker admin | https://jobs.spliit.localhost | http://localhost:3003 |
| MCP | https://mcp.spliit.localhost | http://localhost:3002 |
| Relay | https://relay.spliit.localhost | http://localhost:8787 |
| PostgreSQL | localhost:5432 | localhost:5432 |
| MaxIO | http://localhost:9000/ui/ | http://localhost:9000/ui/ |
| MailDev | http://localhost:1080 | http://localhost:1080 |
Infra (PostgreSQL, MaxIO, MailDev) stays on localhost ports in both modes.
The bypass also needs the https URL envs overridden back to plain ports
(VITE_API_URL + BETTER_AUTH_URL → http://localhost:3001,
WEB_ORIGINS → http://localhost:3000) with the proxy stopped.
Switching to the https names changes the OAuth callback origins to
https://api.spliit.localhost/auth/callback/... — re-register them in
your Google/GitHub provider consoles. Existing local sessions/cookies are
invalidated by the origin change; sign in again.
Opt-in only (not started by bun dev — run manually when needed):
bun --filter @spliit/mcp dev:mcp # MCP on https://mcp.spliit.localhost
bun --filter @spliit/webhook-relay dev:relay # Cloudflare relay on https://relay.spliit.localhost
bun dev:down stops the local service containers. Remove storage/ for a
clean reset of all local service state.
State for the local services lives under storage/ at the repo root. The
MaxIO bucket CORS/public-read config lives at
storage/maxio/buckets/spliit-local/.bucket.json; other local service state is
ignored by Git.
Download compose.yaml and container.env.example, or clone this
repository.
Copy container.env.example to container.env.
Set APP_URL, POSTGRES_PASSWORD, BETTER_AUTH_SECRET, the email delivery
settings (SMTP, or Cloudflare Email Sending via CF_EMAIL_* on hosts that
filter SMTP ports), EMAIL_FROM, and EMAIL_UNSUBSCRIBE_SECRET (delivery
settings are optional when ENABLE_EMAIL_AUTH=false).
Start the stack:
docker compose --env-file container.env up -d
The web gateway listens on 127.0.0.1:3000 by default. Configure your reverse
proxy to forward the public APP_URL to that address. Change WEB_PORT when
needed; set BIND_ADDRESS=0.0.0.0 only when the port must be reachable beyond
the local host.
The API, worker, and database remain private. Migrations run automatically
before the API starts, and PostgreSQL data is stored in the postgres_data
volume.
To expose the API directly for debugging or an intentional split-origin deployment:
docker compose \
--env-file container.env \
-f compose.yaml \
-f compose.api-port.yaml \
up -d
Developers can build the same stack from source with
-f compose.yaml -f compose.build.yaml.
Images are published to GHCR as immutable :vX.Y.Z tags for every release,
with :latest tracking the newest stable release (never a main snapshot).
Pin SPLIIT_TAG=vX.Y.Z in container.env for controlled upgrades, or follow
stable with SPLIIT_TAG=latest.
See docs/deployment.md for configuration, reverse-proxy,
upgrade, backup, and optional-feature guidance. For a private instance, set
SIGNUP_MODE=invite_only in container.env so only invited people can create
accounts.
Key requirements for a public instance:
BETTER_AUTH_SECRET generated with openssl rand -base64 32EMAIL_UNSUBSCRIBE_SECRET generated with openssl rand -hex 32APP_URLENABLE_EMAIL_AUTH=false and no delivery configured —
see docs/deployment.md)SIGNUP_MODE=invite_only so only invited people can create accounts (the first user on a fresh instance can always register)The application has a health check endpoint that can be used to check if the application is running and if the database is accessible.
GET /health/readiness or GET /health - Check if the API is ready to serve requests, including database connectivity.GET /health/liveness - Check if the API process is running, but not necessarily ready to serve requests.Spliit Cloud offers users to upload images (to an AWS S3 bucket) and attach them to expenses. To enable this feature:
PUBLIC_ENABLE_EXPENSE_DOCUMENTS=true
S3_UPLOAD_KEY=AAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_SECRET=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_BUCKET=name-of-s3-bucket
S3_UPLOAD_REGION=us-east-1
S3_UPLOAD_PUBLIC_URL=https://uploads.example.com
You can also use other S3 providers by providing a custom endpoint:
S3_UPLOAD_ENDPOINT=http://localhost:9000
S3_UPLOAD_ENDPOINT is used for signing uploads. S3_UPLOAD_PUBLIC_URL is an
optional browser-readable base URL stored on expense documents and must serve
objects by key, for example https://uploads.example.com/document-...jpg. If it
is not configured, documents use the default AWS S3 public URL format.
Browser uploads go directly to S3 via presigned URLs, so the bucket must allow
cross-origin requests from the web app origin. Without a CORS rule the browser
blocks the upload preflight (CORS Missing Allow Origin) and document uploads
— including AI receipt scans — fail. Allow the APP_URL / WEB_ORIGINS
origins with methods GET, PUT, POST, DELETE, HEAD, headers *, exposed
headers ETag, and MaxAgeSeconds of 3600. Local MaxIO dev storage ships
this config in storage/maxio/buckets/spliit-local/.bucket.json; restart the
maxio service after changing it.
Configure an object lifecycle rule for the tmp/imports/ prefix with a
one-day expiration. The import flow deletes its temporary copies after a
successful database commit, while the lifecycle rule cleans up abandoned or
interrupted imports.
You can offer users to create expense by uploading a receipt. This feature relies on an AI provider and a public S3 storage endpoint.
To enable the feature:
PUBLIC_ENABLE_RECEIPT_EXTRACT=true
AI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The model used for receipt extraction defaults to gpt-5-nano. Override it with AI_RECEIPT_MODEL.
Receipts can be photos or documents: PDF invoices plus TXT, CSV, DOCX, and ODT files. Digital documents extract their embedded text directly (first 3 pages, truncated past 15,000 characters) and structure it with a fast text-only call; scanned-image PDFs go through the OCR backend below, or the vision LLM when none is configured. Documents without usable text keep the file attached and say so explicitly, so the expense can be filled in manually.
Receipt scans time out after AI_RECEIPT_TIMEOUT_SECONDS (default 120). A timeout surfaces as an explicit error in the app instead of hanging silently — if you self-host a slow model, raise the value so scans can finish.
Set TRACING_ENABLED=true to log one human-readable timing tree per AI scan/voice request (step durations, routing branch like ocr+separate(system-one), token usage) to the API console. Off by default; enable when debugging slow scans.
By default the receipt goes straight to the vision LLM (PDFs are sent as documents). To scan faster and cheaper, set AI_OCR_PROVIDER=ocrspace so raw text is extracted by OCR.space (Engine 2 with language autodetect, so traveling receipts work regardless of app language) and structured by a fast text-only call to AI_RECEIPT_MODEL:
AI_OCR_PROVIDER=ocrspace
AI_OCRSPACE_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Optional tuning: AI_OCRSPACE_TIMEOUT_SECONDS (default 30) bounds the OCR request, and AI_OCR_REQUIRED=true fails the scan when OCR fails instead of falling back to the vision LLM (the default false falls back, so a free-tier quota error never breaks scanning). The OCR abstraction is provider-agnostic — adding another backend only needs a new adapter, no procedure changes. Note the free OCR.space tier caps files at 1 MB with rate limits and reads at most 3 PDF pages; receipt files are sent to OCR.space when this is enabled.
You can offer users to automatically deduce the expense category from the title. This feature relies on an AI provider; follow the signup instructions above and configure the following environment variables:
PUBLIC_ENABLE_CATEGORY_EXTRACT=true
AI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The model used for category extraction defaults to gpt-5-nano. Override it with AI_CATEGORY_MODEL.
Voice extraction uses AI_VOICE_MODEL with a timeout of AI_VOICE_TIMEOUT_SECONDS (default 120); inline category suggestions time out after AI_CATEGORY_TIMEOUT_SECONDS (default 30) and quietly fall back to no suggestion.
Suggestions run in stages: the local dictionary (brands/aliases), then group title history, then one AI engine. Each local stage has a deployment switch (CATEGORY_DICTIONARY_ENABLED and CATEGORY_HISTORY_ENABLED, both default true); the local-matcher gates are tunable via CATEGORY_LOCAL_MIN_SCORE (default 0.8) and CATEGORY_LOCAL_SETTLEMENT_MIN_SCORE (default 0.95). The web client mirrors the switches automatically, so both layers agree on which stages run.
AI_CATEGORY_ENGINE selects which AI backend classifies a title when the local stages miss: llm (default, the configured AI_PROVIDER) or system-one (a System One decision-model Choice judgment — TypeSafe's Jev by default). The choice is exclusive — exactly one engine runs per suggestion. The LLM engine needs a model with JSON-mode support (it returns a structured verdict with self-reported confidence); unparsable verdicts fall back to plain-text ID extraction with confidence 0.
To use System One, set the engine and the server-side key (never exposed to the web client):
PUBLIC_ENABLE_CATEGORY_EXTRACT=true
AI_CATEGORY_ENGINE=system-one
AI_SYSTEM_ONE_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Optional overrides: AI_SYSTEM_ONE_MODEL (default jev-latest; pin a versioned ID such as jev-1.13.0 once you tune thresholds, since the jev-latest alias moves), AI_SYSTEM_ONE_TIMEOUT_SECONDS (default 10), and AI_SYSTEM_ONE_BASE_URL (default TypeSafe's endpoint; point at a self-hosted /v1/systemone-compatible server such as Kev to run another decision model — experimental, recalibrate the floor for it). A self-hosted endpoint must use HTTPS; plaintext HTTP is only accepted for local loopback development (http://localhost, http://127.0.0.1).
Both engines report a confidence with each verdict (System One: model confidence; LLM: self-reported confidence in its structured verdict) and share one floor: AI_CATEGORY_MIN_CONFIDENCE (default 0.5). Below-floor verdicts degrade to no suggestion. The two confidences are not on the same scale — recalibrate the floor on a labeled sample of real expenses when switching engines.
System One validates every Choice answer at runtime before the floor applies: the answer must be type: choice, the choice must be allowlisted, confidence must be finite in [0,1], and the probability distribution must be a non-empty object with only allowed keys, finite [0,1] values, and the winner present. Anything malformed becomes no suggestion (bulk runs skip that expense).
What reaches the configured System One endpoint (credentials stay server-side, never in the web client):
suggestCategoryWithSystemOne): truncated title (40 chars), app language, group name/currency, and recent title/category pairs — plus, for receipt scans, OCR line-item titles (top 10) and the receipt currency.categorizeExpensesWithSystemOne, up to 5 questions per request): per expense title (80-char slice) plus expense date; shared group name (100 chars) and locale; per-expense confirmed/recent examples (top 8), title-matched rejected categories, and opt-in nearby categorized expenses (±7 days, up to 3 before + 3 after with title/category/date).Single suggestions run only when the user opts into AI extract; bulk runs are group-admin gated. Suggestion paths log no expense titles or group IDs — bulk run logs record only run lifecycle counts, phases, and durations.
Set AI_PROVIDER to select the request protocol. Provider selection is explicit and is never inferred from a model ID:
AI_PROVIDER=openai-compatible
AI_BASE_URL=https://openrouter.ai/api/v1
AI_RECEIPT_MODEL=openai/gpt-4o-mini
AI_CATEGORY_MODEL=openai/gpt-4o-mini
Supported providers are openai (Responses API), anthropic (Messages API), openai-compatible (Chat Completions API), and google (Gemini API). AI_BASE_URL is optional and must be the API root; the selected SDK adapter appends its endpoint path.
Each purpose can override the shared config with AI_{RECEIPT,CATEGORY,VOICE}_PROVIDER, AI_{RECEIPT,CATEGORY,VOICE}_BASE_URL, and AI_{RECEIPT,CATEGORY,VOICE}_API_KEY (each falls back to AI_PROVIDER / AI_BASE_URL / AI_API_KEY when empty), so one instance can scan receipts through an OpenAI-compatible gateway path while categorizing with Anthropic:
AI_PROVIDER=openai-compatible
AI_BASE_URL=https://openrouter.ai/api/v1
AI_CATEGORY_PROVIDER=anthropic
AI_CATEGORY_BASE_URL=https://opencode.ai/zen/go/v1/messages
AI_CATEGORY_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The provider must match the endpoint's wire. Voice extraction works with any provider; when the resolved provider is openai it uses the Chat Completions API (the Responses API rejects audio input).
Reasoning effort is operator-configured and shared the same way: AI_REASONING is the default and AI_{RECEIPT,CATEGORY,VOICE}_REASONING (none|minimal|low|medium|high|xhigh) override it per purpose. Values pass through to the provider, which maps them to its native reasoning setting — support varies per model, so check your model's docs. When both are empty the parameter is omitted and the provider decides.
Scripts and agents authenticate with OAuth 2.1 rather than a browser session.
Scopes are granted per resource and verb (spliit:groups:read, spliit:expenses:read, and friends), and the two delete scopes are never
part of the default grant, so an agent cannot remove a group or an expense
unless you asked for it. Edits that destroy data, such as shortening a recurring
series, need the delete scope too. Connected apps can be reviewed and
disconnected from account settings. Discovery works from just the API or web
origin (/.well-known/api-catalog, /agent-card.json, /auth.md), with a manual
copy-back page (/oauth/manual-callback) for CLIs that cannot receive a redirect.
See docs/api-access.md for registering a client, obtaining a token, and the full scope list. The interactive reference lives at api.spliit.cloud/docs.
Spliit Cloud can import and export group and account data so you keep ownership of it.
Import:
spliit.app and self-hosted) group exports, including documentsExport:
See docs/migration.md for the step-by-step migration guide from original Spliit.
The project is open to contributions. Feel free to open an issue or even a pull request!
See CONTRIBUTING.md for the workflow, local setup, and PR expectations.
Financial support links are TBD. See Support the project for non-financial ways to help.
bun run test.bun run check; CI should not be the first place a static error surfaces.No. Spliit Cloud is an independent community fork of Spliit. The original Spliit project was created by Sebastien Castiel.
That was the original intention. After submitting fixes and reviewing existing issues and pull requests at the time, the original project appeared to have slowed down, so this fork kept the work moving with account-bound groups and a different stack.
Upstream has since become active again (1.20–1.26). Merging back is no longer practical: hundreds of people depend on spliit.cloud, the data model (account-bound groups vs URL-identified local groups) and the stack (Vite + Hono + tRPC vs Next.js) have diverged, and a migration would break existing groups. Instead this fork ports useful upstream work with credit and keeps the comparison table honest about where upstream has caught up.
The code is open-source under the MIT license. The public hosted instance is currently provided as a community service. Long-term hosting/support details may evolve.
Yes. Self-hosting is supported. See the local and container setup instructions below.
Yes. Import of spliit.app group exports is supported today; see docs/migration.md for the step-by-step. Self-hosted Spliit instances can be migrated by exporting each group and importing it into Spliit Cloud. Splitwise CSV, bank-statement CSVs, Cospend projects, and Spliit Cloud bundles (groups, accounts, and friend ledgers) can be imported the same way.
You can also export Spliit Cloud data as CSV, printable PDF reports, and ZIP bundles that include documents.
No. Groups in Spliit Cloud are always bound to an account so membership, edits, and responsibility stay clear, but that account does not have to be tied to email or a social provider.
You can create an anonymous account in a few clicks: no email address, no Google/GitHub/OIDC — just a display name. You keep a recovery link and can add a passkey instead or alongside it, so the account works across devices and survives a lost browser. The group still sees a real member with authorization, so actions are attributable instead of living behind a shared URL that anyone can edit.
That is different from original Spliit's local-only groups, which lived entirely in the browser and were identified by a URL or group ID. In practice that led to:
I tried supporting both local and synced groups in spliit-app/spliit#495, but the dual model became too complex to build and too complex to explain. Account-bound groups give Spliit Cloud a simpler mental model, real ownership, easier collaboration, and a foundation for features like member management and notifications. Local-only groups are probably not coming back. Anonymous accounts are the way to keep that authorization model without forcing people onto email or social login.
Not yet. End-to-end encrypted groups and expenses are on the roadmap.
Yes. Issues, bug reports, tests, documentation, translations, and pull requests are welcome.
For now, the best ways to support Spliit Cloud are:
Spliit Cloud relies on a small set of external services for features that are impractical to run entirely in-house (exchange rates, email delivery, AI inference, and similar). This section credits the providers we use today and lists openings where we are looking for partners who can offer sustainable free or sponsored capacity for the public instance.
If you run a service that could help and are willing to support Spliit Cloud, email contact@spliit.cloud.
| Provider | What we use it for | Notes |
|---|---|---|
| Frankfurter | Currency exchange rates for expense conversion and the currency converter | Thank you for the generous free API quotas — rates for the public instance are provided via frankfurter.dev. |
| Need | Status | Details |
|---|---|---|
| SMTP / email delivery | Open | Transactional email for invitations, notifications, and account flows. |
| AI inference | Open | Inference capacity for receipt scanning, category suggestions, and related AI features. |
Other providers may be listed here as the stack grows.
MIT, see LICENSE.
59 followers · starred Jul 2026
45 followers · starred Jul 2026
107 followers · starred Aug 2026
Free and Open Source Alternative to Splitwise. Share expenses with your friends and family.
See the codeSpliit Cloud is a community-maintained fork of Spliit: a free, open-source expense splitting app for groups, trips, roommates, friends, and shared costs.
No email or social login required: start with an anonymous account in a few clicks — no email address, no Google/GitHub/OIDC — and keep it recoverable with a recovery link or a passkey.
It keeps the simplicity of the original Spliit while fixing its biggest weakness: groups that lived only in the browser and never synced properly across devices. Cloud accounts give you reliable group syncing, stronger tests, and a more maintainable stack — without forcing everyone onto email.
Public instance: https://spliit.cloud — no email or social account needed. Choose the anonymous option at sign-up (a display name plus a recovery link or passkey) and you get synced groups on all your devices.
Live uptime & incident status at https://status.spliit.cloud
You can also self-host your own instance. See Self-hosting overview, Run locally, and Run in a container.
[!IMPORTANT] The public instance is provided as a community-hosted service. If you need full control over data, uptime, backups, or privacy, self-hosting is recommended.

Spliit Cloud is a community-maintained fork of Spliit, the open-source expense splitter originally created by Sebastien Castiel. It aims to keep the lightweight, no-frills experience that made Spliit popular while evolving the product toward reliable multi-device sync and a stack that is easier to operate and self-host.
People loved that original Spliit needed no account — and hated that their groups lived only in one browser, vanished when site data was cleared, and never synced to their phone. Spliit Cloud is the middle ground: an anonymous account takes a few clicks and asks for no email or social identity, yet your groups follow you across devices and every edit is attributable to a real member. Add email, social login, or a passkey later if you want — or never.
The public instance lives at spliit.cloud: the web app runs on Cloudflare Pages, the API runs on a Hetzner VPS via Dokploy with PostgreSQL on the same VPS, database backups are written to a dedicated Cloudflare R2 bucket, and asset uploads are stored in a separate Cloudflare R2 bucket.
Spliit Cloud exists because I liked Spliit and wanted to keep using it with my friends.
The original Spliit project, created by Sebastien Castiel, is a clean and useful open-source alternative to Splitwise. I first looked at contributing improvements upstream, but after submitting fixes and reviewing existing issues and pull requests at the time, the project appeared to have slowed down.
Since then upstream has become active again (see Relationship to Spliit): 1.20–1.26 shipped global balances, expanded stats, PWA offline assets, monthly category visuals, and security hardening. The stacks and data models have diverged too far to merge back, and hundreds of people now depend on spliit.cloud — so this fork continues as its own line while porting upstream prior art where it fits.
This fork is meant to continue that work openly, with proper credit to the original author and project.
The main things I wanted to improve are:
Spliit Cloud ships only account-bound groups. I explored supporting both local and synced groups in spliit-app/spliit#495, but the dual model became hard to implement and hard to explain. Binding groups to accounts keeps the mental model simple, the data secure, and every action attributable to a member. You do not have to use email or social login: create an anonymous account in a few clicks — just a display name, no email address, no Google/GitHub/OIDC — and keep it with a recovery link or a passkey. You get all the benefits of synced groups with the same simplicity people loved about account-free Spliit, while the group still sees a real member with authorization instead of a shared URL anyone can edit. See the FAQ for the full reasoning.
Spliit Cloud is not affiliated with the original Spliit project unless stated otherwise.
Spliit Cloud is a community fork of spliit-app/spliit.
Credit for the original idea, design, and foundation belongs to the original Spliit project and its creator, Sebastien Castiel.
This fork keeps the project open-source and aims to continue development in a direction focused on accounts, syncing, maintainability, and self-hosting.
Upstream is actively maintained again: since August 2026 it has shipped 1.20–1.26 with real improvements (global balances, expanded group stats with date ranges and drill-downs, monthly category visuals, PWA service worker with offline assets, settle-up in a non-group currency, configurable OpenAI endpoint/models, security headers/CSP, and even-split previews). Switching back is no longer practical — hundreds of people depend on spliit.cloud, groups here are account-bound rather than URL-identified (with anonymous accounts preserving the no-email simplicity), and the stacks have diverged (Vite + React SPA with Hono + tRPC here, Next.js upstream). Where upstream ships useful prior art, this fork ports and credits it instead of pretending the gap is still one-sided; the feature table below reflects that parity honestly.
Spliit Cloud may be useful if you want:
| Feature | Why it matters | Spliit Cloud | Original Spliit |
|---|---|---|---|
| Core Spliit features | Groups, categories, receipts and drag-and-drop attachments, balances, settlements, advanced splits, PWA support, and no ads. Upstream added an offline-asset service worker with update prompt in 1.23.0. | ✅ | ✅ |
| Accounts, friends, and synced groups | Sign in with email, passkeys, optional OIDC/OAuth, or an anonymous account with no email or social login. Profile avatars, 1-on-1 friend ledgers, password set/change, email change, last-used sign-in badge, and groups that follow you across devices—with every action attributable to an account. | ✅ | ❌ |
| Passkeys and account security | Passwordless sign-in for every account type (including guests), backup sign-in choice at signup, per-instance ENABLE_PASSKEY_AUTH kill switch, invite-only SIGNUP_MODE for private instances. | ✅ | ❌ |
| Import and export | Import Splitwise CSV, bank-statement CSVs (mapping wizard, duplicate detection), Cospend projects, original Spliit, and Spliit Cloud bundles; export CSV, printable PDF reports, and full group or account ZIP archives including documents. Upstream added notes/history to JSON export in 1.21.0. | ✅ | ❓ (group JSON export only; Splitwise #483 / Tricount #526 still open) |
| Reliable currency handling | Multi-currency expenses with server-side conversion, a conversion widget, exact group-currency amount override (fees included, implied rate shown), and cryptocurrency support. Upstream fixed the Frankfurter endpoint and minor-units bugs and added settle-in-other-currency in 1.20–1.23. | ✅ | ❓ (fixed basics, no server conversion widget or crypto) |
| Multiple payers and itemized expenses | Track several payers, line items, tax, tip, proportional-to-items remainder splits, and precise per-person shares in one expense. | ✅ | ❌ |
| Durable recurring expenses | Create real recurrence series with intervals, date/count/indefinite endings, catch-up, retries, history, previews, navigation, and explicit stop/edit/delete controls—not read-time side effects. Upstream recurrence got E2E and date fixes but no durable series. | ✅ | ❓ (basic recurrence only) |
| Activity, comments, and discovery | Per-expense change history and comments, For-you/All involving timeline, manual date entry, archived-group search, plus preview, filtering, sorting, and search across groups—without opening the edit form. | ✅ | ❓ |
| Sharing and group identity | Multi-use 15-minute Nearby QR invites with Scan-to-join, group emoji and colors, public view-only links, and member management. Upstream rebased QR sharing in 1.22.0. | ✅ | ❓ (QR sharing only) |
| Outbound webhooks | Subscribe any HTTPS endpoint to signed expense.created/updated/deleted events with retries, delivery history, redelivery, involved-only filtering, and per-endpoint secrets. | ✅ | ❌ |
| Email and push notifications | Deliver group and expense updates through email or push, with per-user preferences for which categories and channels are enabled. SSO-only instances (ENABLE_EMAIL_AUTH=false) run without SMTP. | ✅ | ❌ |
| Stats, charts, and budgets | Per-group spending charts, monthly category visuals, cross-group roll-up, plus weekly, monthly, yearly, or custom budgets by category and participant, with over-budget notifications. Upstream added global balance, expanded stats, drill-downs, and monthly visuals v1 in 1.23–1.25. | ✅ | ❓ (stats without budgets) |
| Balances, settlements, and subgroups | Switch between balance views, settle faster, combine compatible payments, or settle as subgroup units such as couples. Upstream added by-amount remainder display and even-split previews in 1.25–1.26. | ✅ | ❓ (basic balances only) |
| Full localization | Supported languages have complete and a maintained i18n validation workflow, with sparse overlays and fallback chains (e.g. en-GB, pt-BR). Upstream uses Weblate with steady translation activity. | ✅ | ❓ (Weblate, no audited-complete workflow) |
| OpenAPI, Scalar, MCP, and OAuth | Explore the published API interactively, consume the OpenAPI spec, create expenses from ChatGPT and Claude through the optional MCP assistant, or connect scripts and agents via delegated OAuth 2.1 with per-resource scopes and connected-app controls. | ✅ | ❌ |
| Versioned releases and images | Every release publishes immutable :vX.Y.Z GHCR images for all services with :latest tracking the newest stable release; SPLIIT_TAG pins upgrades and the public instance deploys per release. Upstream ships standalone/runtime images with feature flags. | ✅ | ❓ (images, no versioned release train) |
| Group archive & delete | Archive groups to make them view-only, or permanently delete them when no longer needed. | ✅ | ❌ |
| AI-assisted expense workflows | Categorize from the title via local dictionary, group history, then one AI engine (LLM or System One Jev with AI_CATEGORY_ENGINE), uncertain guesses as one-tap chips, per-user opt-out; scan receipts and describe expenses by voice; extracted details stay up for review before saving. | ✅ | ❓ (configurable OpenAI endpoint/models since 1.23.0) |
| Expense amount calculator | A calculator widget in the expense amount field for quick arithmetic while entering expenses. | ✅ | ❌ |
| Responsive mobile experience | Mobile-specific layouts and interaction patterns improve the experience beyond simply narrowing the desktop UI. Upstream added mobile tab icons in 1.23.0. | ✅ | ❓ (mostly a narrow desktop app) |
| Active maintenance | New features, fixes, and self-hosting improvements continue to move forward — see releases. Upstream is active again too (1.20–1.26 in Aug–Sep 2026 with stats, PWA, security, and perf work). | ✅ | ✅ |
The detailed roadmap is the source of truth. Recent work shipped imports (bank CSV, Cospend), delegated OAuth for agents, Nearby QR invites, outbound webhooks, group emoji/colors, passkeys, and System One categorization. Next up are privacy/trust features (end-to-end encryption and offline support), expanded integrations, and self-hosting polish.
Spliit Cloud is still evolving. Current limitations may include:
Please open an issue if you hit a bug or if a missing feature blocks your usage.
Spliit Cloud stores expense data needed to make the app work, including groups, participants, expenses, balances, and uploaded expense documents if that feature is enabled.
The public instance is hosted as follows:
For users who want full control over data and infrastructure, self-hosting is supported. See PRIVACY.md for the detailed data-handling notes and Self-hosting overview for running your own instance.
If you discover a security issue, please follow the responsible disclosure process in SECURITY.md instead of opening a public issue.
The supported Docker setup runs the web app, API, background worker, migrations, and PostgreSQL as one Compose project. The web container is the only public entry point: it serves the SPA and proxies API/auth requests over the private Docker network. Point any HTTPS reverse proxy at the web port.
By default, SMTP is required for sign-in links, email verification, recovery,
and invitations. Set ENABLE_EMAIL_AUTH=false for an SSO-only instance: the email
form is hidden, email endpoints are rejected, and SMTP becomes optional — see
docs/deployment.md. S3-compatible document storage, AI
features, OAuth providers, Web Push, and the MCP assistant are optional.
Prerequisites: Bun, Docker, Node 24+, and portless
(npm install -g portless, then portless trust once so browsers accept the
local HTTPS certificate).
Clone the repository (or your fork if you intend to contribute).
Run bun install to install dependencies.
Copy .env.example to .env (cp .env.example .env).
Start local services, run Prisma operations, and start the app servers:
bun dev:up # postgres, maxio, maildev via compose.dev.yaml
bun prisma-migrate
sudo portless proxy start -p 443 --https # once per boot; serves the https names below
bun dev # web, api, worker admin via portless
Pre-starting the proxy matters: otherwise each service auto-starts the
default plain-HTTP proxy and the https origins won't resolve. Verify with
portless list. People without portless installed can run the underlying
commands directly (dev:app / preview:app / dev:relay:app scripts).
| Service | URL (portless) | Bypass (PORTLESS=0) |
|---|---|---|
| Web | https://spliit.localhost | http://localhost:3000 |
| Web preview | https://preview.spliit.localhost | http://localhost:4173 |
| API | https://api.spliit.localhost | http://localhost:3001 |
| Worker admin | https://jobs.spliit.localhost | http://localhost:3003 |
| MCP | https://mcp.spliit.localhost | http://localhost:3002 |
| Relay | https://relay.spliit.localhost | http://localhost:8787 |
| PostgreSQL | localhost:5432 | localhost:5432 |
| MaxIO | http://localhost:9000/ui/ | http://localhost:9000/ui/ |
| MailDev | http://localhost:1080 | http://localhost:1080 |
Infra (PostgreSQL, MaxIO, MailDev) stays on localhost ports in both modes.
The bypass also needs the https URL envs overridden back to plain ports
(VITE_API_URL + BETTER_AUTH_URL → http://localhost:3001,
WEB_ORIGINS → http://localhost:3000) with the proxy stopped.
Switching to the https names changes the OAuth callback origins to
https://api.spliit.localhost/auth/callback/... — re-register them in
your Google/GitHub provider consoles. Existing local sessions/cookies are
invalidated by the origin change; sign in again.
Opt-in only (not started by bun dev — run manually when needed):
bun --filter @spliit/mcp dev:mcp # MCP on https://mcp.spliit.localhost
bun --filter @spliit/webhook-relay dev:relay # Cloudflare relay on https://relay.spliit.localhost
bun dev:down stops the local service containers. Remove storage/ for a
clean reset of all local service state.
State for the local services lives under storage/ at the repo root. The
MaxIO bucket CORS/public-read config lives at
storage/maxio/buckets/spliit-local/.bucket.json; other local service state is
ignored by Git.
Download compose.yaml and container.env.example, or clone this
repository.
Copy container.env.example to container.env.
Set APP_URL, POSTGRES_PASSWORD, BETTER_AUTH_SECRET, the email delivery
settings (SMTP, or Cloudflare Email Sending via CF_EMAIL_* on hosts that
filter SMTP ports), EMAIL_FROM, and EMAIL_UNSUBSCRIBE_SECRET (delivery
settings are optional when ENABLE_EMAIL_AUTH=false).
Start the stack:
docker compose --env-file container.env up -d
The web gateway listens on 127.0.0.1:3000 by default. Configure your reverse
proxy to forward the public APP_URL to that address. Change WEB_PORT when
needed; set BIND_ADDRESS=0.0.0.0 only when the port must be reachable beyond
the local host.
The API, worker, and database remain private. Migrations run automatically
before the API starts, and PostgreSQL data is stored in the postgres_data
volume.
To expose the API directly for debugging or an intentional split-origin deployment:
docker compose \
--env-file container.env \
-f compose.yaml \
-f compose.api-port.yaml \
up -d
Developers can build the same stack from source with
-f compose.yaml -f compose.build.yaml.
Images are published to GHCR as immutable :vX.Y.Z tags for every release,
with :latest tracking the newest stable release (never a main snapshot).
Pin SPLIIT_TAG=vX.Y.Z in container.env for controlled upgrades, or follow
stable with SPLIIT_TAG=latest.
See docs/deployment.md for configuration, reverse-proxy,
upgrade, backup, and optional-feature guidance. For a private instance, set
SIGNUP_MODE=invite_only in container.env so only invited people can create
accounts.
Key requirements for a public instance:
BETTER_AUTH_SECRET generated with openssl rand -base64 32EMAIL_UNSUBSCRIBE_SECRET generated with openssl rand -hex 32APP_URLENABLE_EMAIL_AUTH=false and no delivery configured —
see docs/deployment.md)SIGNUP_MODE=invite_only so only invited people can create accounts (the first user on a fresh instance can always register)The application has a health check endpoint that can be used to check if the application is running and if the database is accessible.
GET /health/readiness or GET /health - Check if the API is ready to serve requests, including database connectivity.GET /health/liveness - Check if the API process is running, but not necessarily ready to serve requests.Spliit Cloud offers users to upload images (to an AWS S3 bucket) and attach them to expenses. To enable this feature:
PUBLIC_ENABLE_EXPENSE_DOCUMENTS=true
S3_UPLOAD_KEY=AAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_SECRET=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_BUCKET=name-of-s3-bucket
S3_UPLOAD_REGION=us-east-1
S3_UPLOAD_PUBLIC_URL=https://uploads.example.com
You can also use other S3 providers by providing a custom endpoint:
S3_UPLOAD_ENDPOINT=http://localhost:9000
S3_UPLOAD_ENDPOINT is used for signing uploads. S3_UPLOAD_PUBLIC_URL is an
optional browser-readable base URL stored on expense documents and must serve
objects by key, for example https://uploads.example.com/document-...jpg. If it
is not configured, documents use the default AWS S3 public URL format.
Browser uploads go directly to S3 via presigned URLs, so the bucket must allow
cross-origin requests from the web app origin. Without a CORS rule the browser
blocks the upload preflight (CORS Missing Allow Origin) and document uploads
— including AI receipt scans — fail. Allow the APP_URL / WEB_ORIGINS
origins with methods GET, PUT, POST, DELETE, HEAD, headers *, exposed
headers ETag, and MaxAgeSeconds of 3600. Local MaxIO dev storage ships
this config in storage/maxio/buckets/spliit-local/.bucket.json; restart the
maxio service after changing it.
Configure an object lifecycle rule for the tmp/imports/ prefix with a
one-day expiration. The import flow deletes its temporary copies after a
successful database commit, while the lifecycle rule cleans up abandoned or
interrupted imports.
You can offer users to create expense by uploading a receipt. This feature relies on an AI provider and a public S3 storage endpoint.
To enable the feature:
PUBLIC_ENABLE_RECEIPT_EXTRACT=true
AI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The model used for receipt extraction defaults to gpt-5-nano. Override it with AI_RECEIPT_MODEL.
Receipts can be photos or documents: PDF invoices plus TXT, CSV, DOCX, and ODT files. Digital documents extract their embedded text directly (first 3 pages, truncated past 15,000 characters) and structure it with a fast text-only call; scanned-image PDFs go through the OCR backend below, or the vision LLM when none is configured. Documents without usable text keep the file attached and say so explicitly, so the expense can be filled in manually.
Receipt scans time out after AI_RECEIPT_TIMEOUT_SECONDS (default 120). A timeout surfaces as an explicit error in the app instead of hanging silently — if you self-host a slow model, raise the value so scans can finish.
Set TRACING_ENABLED=true to log one human-readable timing tree per AI scan/voice request (step durations, routing branch like ocr+separate(system-one), token usage) to the API console. Off by default; enable when debugging slow scans.
By default the receipt goes straight to the vision LLM (PDFs are sent as documents). To scan faster and cheaper, set AI_OCR_PROVIDER=ocrspace so raw text is extracted by OCR.space (Engine 2 with language autodetect, so traveling receipts work regardless of app language) and structured by a fast text-only call to AI_RECEIPT_MODEL:
AI_OCR_PROVIDER=ocrspace
AI_OCRSPACE_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Optional tuning: AI_OCRSPACE_TIMEOUT_SECONDS (default 30) bounds the OCR request, and AI_OCR_REQUIRED=true fails the scan when OCR fails instead of falling back to the vision LLM (the default false falls back, so a free-tier quota error never breaks scanning). The OCR abstraction is provider-agnostic — adding another backend only needs a new adapter, no procedure changes. Note the free OCR.space tier caps files at 1 MB with rate limits and reads at most 3 PDF pages; receipt files are sent to OCR.space when this is enabled.
You can offer users to automatically deduce the expense category from the title. This feature relies on an AI provider; follow the signup instructions above and configure the following environment variables:
PUBLIC_ENABLE_CATEGORY_EXTRACT=true
AI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The model used for category extraction defaults to gpt-5-nano. Override it with AI_CATEGORY_MODEL.
Voice extraction uses AI_VOICE_MODEL with a timeout of AI_VOICE_TIMEOUT_SECONDS (default 120); inline category suggestions time out after AI_CATEGORY_TIMEOUT_SECONDS (default 30) and quietly fall back to no suggestion.
Suggestions run in stages: the local dictionary (brands/aliases), then group title history, then one AI engine. Each local stage has a deployment switch (CATEGORY_DICTIONARY_ENABLED and CATEGORY_HISTORY_ENABLED, both default true); the local-matcher gates are tunable via CATEGORY_LOCAL_MIN_SCORE (default 0.8) and CATEGORY_LOCAL_SETTLEMENT_MIN_SCORE (default 0.95). The web client mirrors the switches automatically, so both layers agree on which stages run.
AI_CATEGORY_ENGINE selects which AI backend classifies a title when the local stages miss: llm (default, the configured AI_PROVIDER) or system-one (a System One decision-model Choice judgment — TypeSafe's Jev by default). The choice is exclusive — exactly one engine runs per suggestion. The LLM engine needs a model with JSON-mode support (it returns a structured verdict with self-reported confidence); unparsable verdicts fall back to plain-text ID extraction with confidence 0.
To use System One, set the engine and the server-side key (never exposed to the web client):
PUBLIC_ENABLE_CATEGORY_EXTRACT=true
AI_CATEGORY_ENGINE=system-one
AI_SYSTEM_ONE_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
Optional overrides: AI_SYSTEM_ONE_MODEL (default jev-latest; pin a versioned ID such as jev-1.13.0 once you tune thresholds, since the jev-latest alias moves), AI_SYSTEM_ONE_TIMEOUT_SECONDS (default 10), and AI_SYSTEM_ONE_BASE_URL (default TypeSafe's endpoint; point at a self-hosted /v1/systemone-compatible server such as Kev to run another decision model — experimental, recalibrate the floor for it). A self-hosted endpoint must use HTTPS; plaintext HTTP is only accepted for local loopback development (http://localhost, http://127.0.0.1).
Both engines report a confidence with each verdict (System One: model confidence; LLM: self-reported confidence in its structured verdict) and share one floor: AI_CATEGORY_MIN_CONFIDENCE (default 0.5). Below-floor verdicts degrade to no suggestion. The two confidences are not on the same scale — recalibrate the floor on a labeled sample of real expenses when switching engines.
System One validates every Choice answer at runtime before the floor applies: the answer must be type: choice, the choice must be allowlisted, confidence must be finite in [0,1], and the probability distribution must be a non-empty object with only allowed keys, finite [0,1] values, and the winner present. Anything malformed becomes no suggestion (bulk runs skip that expense).
What reaches the configured System One endpoint (credentials stay server-side, never in the web client):
suggestCategoryWithSystemOne): truncated title (40 chars), app language, group name/currency, and recent title/category pairs — plus, for receipt scans, OCR line-item titles (top 10) and the receipt currency.categorizeExpensesWithSystemOne, up to 5 questions per request): per expense title (80-char slice) plus expense date; shared group name (100 chars) and locale; per-expense confirmed/recent examples (top 8), title-matched rejected categories, and opt-in nearby categorized expenses (±7 days, up to 3 before + 3 after with title/category/date).Single suggestions run only when the user opts into AI extract; bulk runs are group-admin gated. Suggestion paths log no expense titles or group IDs — bulk run logs record only run lifecycle counts, phases, and durations.
Set AI_PROVIDER to select the request protocol. Provider selection is explicit and is never inferred from a model ID:
AI_PROVIDER=openai-compatible
AI_BASE_URL=https://openrouter.ai/api/v1
AI_RECEIPT_MODEL=openai/gpt-4o-mini
AI_CATEGORY_MODEL=openai/gpt-4o-mini
Supported providers are openai (Responses API), anthropic (Messages API), openai-compatible (Chat Completions API), and google (Gemini API). AI_BASE_URL is optional and must be the API root; the selected SDK adapter appends its endpoint path.
Each purpose can override the shared config with AI_{RECEIPT,CATEGORY,VOICE}_PROVIDER, AI_{RECEIPT,CATEGORY,VOICE}_BASE_URL, and AI_{RECEIPT,CATEGORY,VOICE}_API_KEY (each falls back to AI_PROVIDER / AI_BASE_URL / AI_API_KEY when empty), so one instance can scan receipts through an OpenAI-compatible gateway path while categorizing with Anthropic:
AI_PROVIDER=openai-compatible
AI_BASE_URL=https://openrouter.ai/api/v1
AI_CATEGORY_PROVIDER=anthropic
AI_CATEGORY_BASE_URL=https://opencode.ai/zen/go/v1/messages
AI_CATEGORY_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
The provider must match the endpoint's wire. Voice extraction works with any provider; when the resolved provider is openai it uses the Chat Completions API (the Responses API rejects audio input).
Reasoning effort is operator-configured and shared the same way: AI_REASONING is the default and AI_{RECEIPT,CATEGORY,VOICE}_REASONING (none|minimal|low|medium|high|xhigh) override it per purpose. Values pass through to the provider, which maps them to its native reasoning setting — support varies per model, so check your model's docs. When both are empty the parameter is omitted and the provider decides.
Scripts and agents authenticate with OAuth 2.1 rather than a browser session.
Scopes are granted per resource and verb (spliit:groups:read, spliit:expenses:read, and friends), and the two delete scopes are never
part of the default grant, so an agent cannot remove a group or an expense
unless you asked for it. Edits that destroy data, such as shortening a recurring
series, need the delete scope too. Connected apps can be reviewed and
disconnected from account settings. Discovery works from just the API or web
origin (/.well-known/api-catalog, /agent-card.json, /auth.md), with a manual
copy-back page (/oauth/manual-callback) for CLIs that cannot receive a redirect.
See docs/api-access.md for registering a client, obtaining a token, and the full scope list. The interactive reference lives at api.spliit.cloud/docs.
Spliit Cloud can import and export group and account data so you keep ownership of it.
Import:
spliit.app and self-hosted) group exports, including documentsExport:
See docs/migration.md for the step-by-step migration guide from original Spliit.
The project is open to contributions. Feel free to open an issue or even a pull request!
See CONTRIBUTING.md for the workflow, local setup, and PR expectations.
Financial support links are TBD. See Support the project for non-financial ways to help.
bun run test.bun run check; CI should not be the first place a static error surfaces.No. Spliit Cloud is an independent community fork of Spliit. The original Spliit project was created by Sebastien Castiel.
That was the original intention. After submitting fixes and reviewing existing issues and pull requests at the time, the original project appeared to have slowed down, so this fork kept the work moving with account-bound groups and a different stack.
Upstream has since become active again (1.20–1.26). Merging back is no longer practical: hundreds of people depend on spliit.cloud, the data model (account-bound groups vs URL-identified local groups) and the stack (Vite + Hono + tRPC vs Next.js) have diverged, and a migration would break existing groups. Instead this fork ports useful upstream work with credit and keeps the comparison table honest about where upstream has caught up.
The code is open-source under the MIT license. The public hosted instance is currently provided as a community service. Long-term hosting/support details may evolve.
Yes. Self-hosting is supported. See the local and container setup instructions below.
Yes. Import of spliit.app group exports is supported today; see docs/migration.md for the step-by-step. Self-hosted Spliit instances can be migrated by exporting each group and importing it into Spliit Cloud. Splitwise CSV, bank-statement CSVs, Cospend projects, and Spliit Cloud bundles (groups, accounts, and friend ledgers) can be imported the same way.
You can also export Spliit Cloud data as CSV, printable PDF reports, and ZIP bundles that include documents.
No. Groups in Spliit Cloud are always bound to an account so membership, edits, and responsibility stay clear, but that account does not have to be tied to email or a social provider.
You can create an anonymous account in a few clicks: no email address, no Google/GitHub/OIDC — just a display name. You keep a recovery link and can add a passkey instead or alongside it, so the account works across devices and survives a lost browser. The group still sees a real member with authorization, so actions are attributable instead of living behind a shared URL that anyone can edit.
That is different from original Spliit's local-only groups, which lived entirely in the browser and were identified by a URL or group ID. In practice that led to:
I tried supporting both local and synced groups in spliit-app/spliit#495, but the dual model became too complex to build and too complex to explain. Account-bound groups give Spliit Cloud a simpler mental model, real ownership, easier collaboration, and a foundation for features like member management and notifications. Local-only groups are probably not coming back. Anonymous accounts are the way to keep that authorization model without forcing people onto email or social login.
Not yet. End-to-end encrypted groups and expenses are on the roadmap.
Yes. Issues, bug reports, tests, documentation, translations, and pull requests are welcome.
For now, the best ways to support Spliit Cloud are:
Spliit Cloud relies on a small set of external services for features that are impractical to run entirely in-house (exchange rates, email delivery, AI inference, and similar). This section credits the providers we use today and lists openings where we are looking for partners who can offer sustainable free or sponsored capacity for the public instance.
If you run a service that could help and are willing to support Spliit Cloud, email contact@spliit.cloud.
| Provider | What we use it for | Notes |
|---|---|---|
| Frankfurter | Currency exchange rates for expense conversion and the currency converter | Thank you for the generous free API quotas — rates for the public instance are provided via frankfurter.dev. |
| Need | Status | Details |
|---|---|---|
| SMTP / email delivery | Open | Transactional email for invitations, notifications, and account flows. |
| AI inference | Open | Inference capacity for receipt scanning, category suggestions, and related AI features. |
Other providers may be listed here as the stack grows.
MIT, see LICENSE.
59 followers · starred Jul 2026
45 followers · starred Jul 2026
107 followers · starred Aug 2026