Skip to main content

Bill splitter and tracker.

Project description

Owe

Owe is a simple web application for splitting bills and tracking who owes whom how much.

Overview

Owe lets a group of users record debts and payments between each other. The Summary tab shows the minimum set of transactions needed to settle all outstanding balances, and provides a one-click Settle button for each.

Core concepts:

  • Users - participants identified by string ID.
  • Records - individual transactions of type DEBT (someone owes money) or PAYMENT (money has been physically transferred). Records can be cancelled (invalidated) if entered by mistake.
  • Summary - the net balances across all active records, reduced to the minimum number of settlement transactions.

Settlement algorithm

Owe computes summaries by first reducing all active records to each user's net balance. Users with zero balance are ignored. For the remaining users, the exact minimum-transfer settlement problem is NP-hard. It is commonly known as the optimal account balancing problem.

Owe uses an exact bitmask dynamic programming algorithm over zero-sum subsets:

  1. Precompute the sum of every subset of nonzero balances.
  2. Partition the users into the maximum possible number of disjoint zero-sum groups.
  3. Settle each zero-sum group independently.

The reason this minimizes transfers is that a zero-sum subset of $k$ nonzero balances can always be settled in exactly $k−1$ transfers, and cannot use fewer. If there are $n$ users with nonzero balances, the total number of transactions is minimized by partitioning them into the largest possible collection of disjoint zero-sum subsets, giving a minimum of $n−k_\text{max}$ transfers, where $k_\text{max}$ is the number of subsets in the optimal partition.

This exact algorithm has exponential worst-case cost in the number of users with nonzero balances, so it is intended for typical small group bill splitting rather than very large accounting ledgers.

Requirements

Owe runs on Python 3.10 or later. It uses SQLite for data storage, so no additional database setup is required.

For development, uv is recommended but pip can also be used.

Setup

Owe is available on PyPI as owe, and as a Homebrew formula zhongruoyu/tap/owe. You can get started as follows:

# With uv
uv tool install owe
# Or run directly with uvx
uvx owe record list

# With pip
pip install owe

# With Homebrew
brew install zhongruoyu/tap/owe

Docker images are available on Docker Hub as zhongruoyu/owe, and on GitHub Container Registry as ghcr.io/zhongruoyu/owe. The latest and named v<version> tags track the stable releases, and the main tag tracks the latest commit on the main branch. You may run Owe with Docker as follows:

docker run --rm -v "$PWD/owe.db":/owe.db zhongruoyu/owe owe record list

This Docker command runs the owe record list command to list all records in the default database file owe.db. Read on for more details on using the command-line interface and running the web server.

Command-line interface

Owe provides a command-line utility, owe, for managing users and records from the command line. Use it as follows:

  • owe [--database <path>] user list [--active] [--format <format>]: List all users, or only active users if --active is specified.
  • owe [--database <path>] user add <id> <name>: Add a user with the given ID and name.
  • owe [--database <path>] user <activate|deactivate> <id>: Activate or deactivate the user with the given ID.
  • owe [--database <path>] record list [--active] [--format <format>]: List all records, or only active records if --active is specified.
  • owe [--database <path>] record add [options...] [--format <format>]: Add a record with the specified options and list the created records. Options:
    • --type <DEBT|PAYMENT>: Type of the record (required).
    • --lender <id>: ID of the lender (required).
    • --borrower <id>: ID of the borrower (required). Pass multiple --borrower for multiple distinct borrowers. The lender may be included for self-share splitting, but at least one borrower must differ from the lender.
    • --amount <amount>: Total amount (will be split evenly among borrowers) (required).
    • --created-by <id>: ID of the user creating the record (required).
    • --remarks <remarks>: Optional remarks for the record.
  • owe [--database <path>] record <activate|cancel> [--id <id> ...]: Update the status of records with the given IDs to active or inactive.
  • owe [--database <path>] record summary [--format <format>]: Show the summary of settlements.

The --database option allows you to choose the SQLite database file; if not specified, it defaults to owe.db in the current directory. --format can be set to table (default) for more human-readable output, or json or csv for more machine-friendly output.

Web server

Owe also includes a server in the owe.app module, which serves the same functionality over a REST API, with an optional web user interface for managing records and users. To get started, you can run the server with uv and an ASGI server like Uvicorn, as follows:

uvx --with owe uvicorn --factory owe.app:create_app

Or if you prefer pip:

pip install owe uvicorn
uvicorn --factory owe.app:create_app

Note that the server does not handle authentication or authorization by itself, so it is strongly recommended to run it in a trusted environment (e.g. behind a reverse proxy with access control or in a trusted local network) to prevent unauthorized access.

Telegram notifications

Owe also supports sending notifications on record creation and alteration to a Telegram chat. To enable this, a Telegram bot must be created and added to the target chat, and the bot token and chat ID must be set in the environment, as described in the next section.

Environment variables

The application can be configured with the following environment variables:

  • OWE_URL_PREFIX: URL prefix for all endpoints (e.g. /owe). Default: unset.
  • OWE_API_ONLY: If set to a non-empty value (e.g. true), the server will serve the API at the specified URL prefix without the web user interface; if unset, the server will serve a web user interface at the URL prefix, and the API will be available under the /api subpath of the URL prefix. Default: unset (i.e. serve the web UI).
  • OWE_LOG_LEVEL: Python logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). Default: INFO.
  • OWE_DATABASE: Path to the SQLite database file. Default: owe.db.
  • OWE_CURRENCY: ISO 4217 currency code displayed in the UI. Default: USD.
  • OWE_REQUEST_ID_HEADER: HTTP header to trust for the authenticated user's ID, to be used as the creator of records. When run behind Cloudflare Access, this can be set to cf-access-authenticated-user-email so that the application can identify the user by their email address. When unset, the remote IP address will be used to identify the user instead. Default: unset.
  • OWE_TRUST_PROXY: Whether to trust the X-Forwarded-For header for the remote IP address. This should be set to a non-empty value (e.g., true) when running behind a reverse proxy that sets this header, and unset otherwise, to prevent IP spoofing. Default: unset (i.e. do not trust the header).
  • OWE_TELEGRAM_BOT_TOKEN: Telegram bot token for notifications. Default: unset.
  • OWE_TELEGRAM_CHAT_ID: Telegram chat ID for notifications. Default: unset.

API Endpoints

The server exposes the following API endpoints:

  • GET /api/config: Get application configuration (e.g. currency).
  • GET /api/users: List all users.
  • GET /api/records: List all active and inactive records.
  • POST /api/records: Create a new record. Request body should be JSON with the following fields:
    • type: "DEBT" or "PAYMENT"
    • lender: ID of the lender
    • borrowers: list of IDs of the borrowers Borrowers must be unique. The lender may be included for self-share splitting, but at least one borrower must differ from the lender.
    • amount: total amount (will be split evenly among borrowers)
    • remarks: optional remarks for the record
  • PATCH /api/records/status: Update the status of records. Request body should be JSON with the following fields:
    • ids: list of record IDs to update
    • active: boolean indicating whether the records should be active or inactive
  • GET /api/summary: Get the minimum set of settlement transactions.

For API endpoints that create or modify records, the creator of the record is determined from the OWE_REQUEST_ID_HEADER header if set, or from the remote IP address otherwise.

Running the web server with Docker

The Docker image includes the Uvicorn ASGI server, which you can use to run the application in production:

docker run -d \
  -p 8000:8000 \
  -v "$PWD/owe.db":/owe.db \
  -e OWE_CURRENCY=USD \
  zhongruoyu/owe \
  uvicorn --factory owe.app:create_app --host 0.0.0.0 --port 8000 --workers 4

License

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

owe-0.2.0.tar.gz (24.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

owe-0.2.0-py3-none-any.whl (30.6 kB view details)

Uploaded Python 3

File details

Details for the file owe-0.2.0.tar.gz.

File metadata

  • Download URL: owe-0.2.0.tar.gz
  • Upload date:
  • Size: 24.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for owe-0.2.0.tar.gz
Algorithm Hash digest
SHA256 1c53dcf7a975b167dbead8e31de00f382ce60839e04dfc7f61d39358d88526e3
MD5 20c6d844b69b3e3bb6a3b420a1370de8
BLAKE2b-256 28189cc1c6e225e59b79de2639852581ceb6fea45b612c05cf638ae62a45c342

See more details on using hashes here.

Provenance

The following attestation bundles were made for owe-0.2.0.tar.gz:

Publisher: release.yml on ZhongRuoyu/owe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file owe-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: owe-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 30.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for owe-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 af3e31b80dd0ce9f295fb91146131fb9901dbebcd33efae529ad02e0e42fa700
MD5 1b22f80bd364ec52dd60474c3cb627ab
BLAKE2b-256 de34d2b6cb91b559aad40da54287753d159d29993f85a289f86903a8787f44d5

See more details on using hashes here.

Provenance

The following attestation bundles were made for owe-0.2.0-py3-none-any.whl:

Publisher: release.yml on ZhongRuoyu/owe

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page