Group expense settle-up
Enter each person's net balance and get a short list of who pays whom: if Alice is owed 50 while Bob owes 30 and Carol owes 20, two payments to Alice clear the whole group. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
After a trip, flat share, or team dinner, everyone knows roughly who paid for what—but translating those nets into actual bank transfers is messy. This group expense settle-up tool takes each person’s net balance (amount paid minus amount owed) and returns a compact list of who should pay whom and how much, so the group is fully cleared without circular IOUs. Positive balances mean someone should receive money; negative balances mean they still owe. The sums must close to zero; if they do not, the calculator rejects the input instead of inventing a silent residual.
What net balances mean and why they must sum to zero
A net balance is the simplest summary of shared expenses: for each person, subtract what they were supposed to cover from what they actually paid. If Alice paid sixty toward a shared bill and her fair share was forty, her net is plus twenty—she should get twenty back. If Bob paid nothing toward a total he still owes thirty of, his net is minus thirty.
When every person in a closed group is listed, the positive nets and negative nets cancel: money that left the group’s pockets must reappear as someone else’s overpayment. That is why this settle-up calculator requires the balances to sum to zero within a tiny numerical tolerance. If the sum is not zero, the sheet is incomplete—someone is missing, a share was mistyped, or currency units were mixed—and inventing transfers would hide the bug.
At least two people are required; a single name cannot settle with themselves. Each name must be unique so the transfer list has no ambiguity about who pays whom. Zero balances are allowed: people who are already even simply do not appear as senders or receivers in the result.
How the minimum transfer list is built
The algorithm is the classic greedy min-cash-flow used by settle-up apps: split the group into debtors (negative balance, they still owe) and creditors (positive balance, they should receive). Working copies track how much each debtor still needs to pay and how much each creditor still needs to collect. On each step, the largest remaining debtor pays as much as possible to the largest remaining creditor, producing one transfer of that amount, then both remaining obligations shrink.
When a person’s remaining amount hits zero they leave the queue; the process continues until every non-zero net is cleared. The number of transfers is at most one less than the number of people with a non-zero balance—far fewer than the naive approach of routing every debt through a single treasurer when many bilateral payments can cancel locally. Optional fields are omitted rather than null.
When this kind of settle-up helps
Trip organizers paste nets from a spreadsheet and text friends a short pay list instead of collecting screenshots of three different apps. Shared flats and co-living groups reconciling monthly utilities want a reproducible plan that still works when one roommate’s net is zero. Hackathon and meetup hosts who front venue or food costs need a fair clear without building Splitwise from scratch.
Product teams embedding group finance in chat bots call the same pure module the free web tool uses, so demos and production never diverge on edge cases like already-settled groups or two-person swaps. Failed validation—missing people, duplicate names, non-finite balances, or a non-zero sum—does not consume balance. Keep requests small, pin the transfer list in tests, and you get a trustworthy who-owes-who plan for events, households, and automation without standing up a full expense ledger of your own.
What you can do with it
Trip and weekend group clear
Convert each traveler’s paid-minus-owed net into a short list of bank transfers so nobody is left holding an IOU after the holiday.
Flatmate monthly utilities
After rent, power, and groceries are tallied, settle residual nets with the fewest room-to-room payments for the month.
FAQ
How do I calculate settle-up transfers from group balances?
Send people as an array of {name, balance} where balance is paid minus owed. Positive means they should receive money; negative means they owe.
What does a positive or negative balance mean?
Positive balance: the person overpaid and should receive money. Negative balance: they underpaid and should pay. Zero means already settled and they won’t appear in transfers.
Is this the same as splitting a bill by weight?
No. A shared-expense splitter turns item amounts and weights into each person’s share. This settle-up tool starts after nets exist and only plans who pays whom.
How many payments does it take to settle a group?
At most one fewer than the number of people whose balance is not zero. With three people owed or owing, two transfers are enough, and anyone already even is left out of the list.
Why must all the balances add up to zero?
Because every amount someone overpaid was underpaid by someone else. If the total is not zero, a person is missing or a share was mistyped, so the calculator stops instead of inventing a leftover.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/math/who-owes-who-settle-up \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"people":[{"name":"Alice","balance":50},{"name":"Bob","balance":-30},{"name":"Carol","balance":-20}]}'const res = await fetch("https://api.kit.forhosting.com/math/who-owes-who-settle-up", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"people": [
{
"name": "Alice",
"balance": 50
},
{
"name": "Bob",
"balance": -30
},
{
"name": "Carol",
"balance": -20
}
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/math/who-owes-who-settle-up",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"people": [
{
"name": "Alice",
"balance": 50
},
{
"name": "Bob",
"balance": -30
},
{
"name": "Carol",
"balance": -20
}
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/math/who-owes-who-settle-up", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"people":[{"name":"Alice","balance":50},{"name":"Bob","balance":-30},{"name":"Carol","balance":-20}]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"people":[{"name":"Alice","balance":50},{"name":"Bob","balance":-30},{"name":"Carol","balance":-20}]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/math/who-owes-who-settle-up", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"people": [
{
"name": "Alice",
"balance": 50
},
{
"name": "Bob",
"balance": -30
},
{
"name": "Carol",
"balance": -20
}
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "math.who_owes_who_settle_up",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
What net balances mean and why they must sum to zero
The endpoint therefore fails with invalid_input and a clear message that the balances must sum to zero, so you can fix the inputs before anyone sends money.
How the minimum transfer list is built
Output fields include person_count, balance_sum (always zero on success), transfer_count, total_transferred (sum of payment amounts), transfers as an array of from, to, and amount, and a short human summary string. Pair this endpoint with your own ledger UI if you need receipts or multi-currency conversion; the capability itself only plans pure numeric transfers in one unit of account.
Integration use cases
Chat-bot and spreadsheet automation
Pipe closed balance rows from a sheet or bot into the API and post the transfer plan back to the group channel.
Technical questions
Why must balances sum to zero?
A closed group’s overpayments and underpayments cancel. A non-zero sum means missing people or a typing error; the task fails with invalid_input instead of inventing residual debt.
What does the group expense settle-up calculator cost?
The browser tool is free. API requests use prepaid KIT balance at $0.002 per successful call; invalid input is not billed.