Split transactions
Record one payment that is divided among several accounts (a pay slip, a shop receipt), then add, change or remove a split while keeping the transaction balanced.
Most transactions have two splits: money leaves one account and arrives in another. A split transaction has more: your salary arrives in the bank as one deposit but is really gross pay less tax and pension; a supermarket receipt is one payment covering food and household goods. Each split names an account and a value, and the values must still sum to exactly zero.
Steps
- Work out the parts. For a 2,000.00 pay slip with 300.00 tax and 100.00 pension, the bank receives 1,600.00. The
parts are: bank
+1600.00, tax expense+300.00, pension expense+100.00, and the salary income account-2000.00. They sum to zero. - Post it with transaction.post, listing every split. A split may carry a
memo, which describes that line only. - Look at it with transaction.get. Every split has a GUID.
- Change a split with transaction.update:
changeSplitstakes the split'sguidand the fields to change,addSplitstakes new splits,removeSplitstakes GUIDs. The result must balance, so a change that leaves the transaction short is refused with unbalanced and nothing changes. Change two splits in one call to keep it balanced. - Remove a split by listing its GUID in
removeSplitsand changing another to take up the difference in the same call.
Example
Example: with a new, empty book open as book.gnucash.
JSON-RPC
Request:
{"jsonrpc":"2.0","id":1,"method":"account.create","params":{"name":"Checking","type":"BANK","commodity":"USD"}}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": "…"
}
Request:
{"jsonrpc":"2.0","id":2,"method":"account.create","params":{"name":"Salary","type":"INCOME","commodity":"USD"}}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"result": "…"
}
Request:
{"jsonrpc":"2.0","id":3,"method":"account.create","params":{"name":"Tax","type":"EXPENSE","commodity":"USD"}}
Response:
{
"jsonrpc": "2.0",
"id": 3,
"result": "…"
}
Request:
{"jsonrpc":"2.0","id":4,"method":"account.create","params":{"name":"Pension","type":"EXPENSE","commodity":"USD"}}
Response:
{
"jsonrpc": "2.0",
"id": 4,
"result": "…"
}
Request:
{"jsonrpc":"2.0","id":5,"method":"transaction.post","params":{"datePosted":"2024-07-01","description":"July pay slip","splits":[{"account":"Checking","value":"1600.00"},{"account":"Tax","value":"300.00","memo":"Income tax"},{"account":"Pension","value":"100.00"},{"account":"Salary","value":"-2000.00"}]}}
Response:
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"guid": "…",
"balanced": true,
"splits": [
"…",
"…",
"…",
"…"
],
"…": "…"
}
}
Command line
red-ale exec --book book.gnucash --script - <<'EOF'
account.create {"name":"Checking","type":"BANK","commodity":"USD"}
account.create {"name":"Salary","type":"INCOME","commodity":"USD"}
account.create {"name":"Tax","type":"EXPENSE","commodity":"USD"}
account.create {"name":"Pension","type":"EXPENSE","commodity":"USD"}
transaction.post {"datePosted":"2024-07-01","description":"July pay slip","splits":[{"account":"Checking","value":"1600.00"},{"account":"Tax","value":"300.00","memo":"Income tax"},{"account":"Pension","value":"100.00"},{"account":"Salary","value":"-2000.00"}]}
EOF
It prints:
[
"…",
"…",
"…",
"…",
{
"guid": "…",
"balanced": true,
"splits": [
"…",
"…",
"…",
"…"
],
"…": "…"
}
]
MCP tool call
Tool call:
{"name":"account.create","arguments":{"name":"Checking","type":"BANK","commodity":"USD"}}
Result:
{
"content": [
{
"type": "text",
"text": "\"…\""
}
]
}
Tool call:
{"name":"account.create","arguments":{"name":"Salary","type":"INCOME","commodity":"USD"}}
Result:
{
"content": [
{
"type": "text",
"text": "\"…\""
}
]
}
Tool call:
{"name":"account.create","arguments":{"name":"Tax","type":"EXPENSE","commodity":"USD"}}
Result:
{
"content": [
{
"type": "text",
"text": "\"…\""
}
]
}
Tool call:
{"name":"account.create","arguments":{"name":"Pension","type":"EXPENSE","commodity":"USD"}}
Result:
{
"content": [
{
"type": "text",
"text": "\"…\""
}
]
}
Tool call:
{"name":"transaction.post","arguments":{"datePosted":"2024-07-01","description":"July pay slip","splits":[{"account":"Checking","value":"1600.00"},{"account":"Tax","value":"300.00","memo":"Income tax"},{"account":"Pension","value":"100.00"},{"account":"Salary","value":"-2000.00"}]}}
Result:
{
"structuredContent": {
"guid": "…",
"balanced": true,
"splits": [
"…",
"…",
"…",
"…"
],
"…": "…"
}
}
To change a split you need its GUID, from transaction.get or the result of the post. This removes the wallet split of a three-split transaction and makes the expense split take the whole 100.00, in one call:
Example: with a copy of the reports/test-data example book open as book.gnucash.
JSON-RPC
Request:
{"jsonrpc":"2.0","id":1,"method":"transaction.update","params":{"guid":"c0000000000000000000000000000007","removeSplits":["d0000000000000000000000000000010"],"changeSplits":[{"guid":"d000000000000000000000000000000f","value":"100.00"}]}}
Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"balanced": true,
"splits": [
{
"accountName": "Root:Expenses",
"value": {
"num": "10000",
"denom": "100"
},
"…": "…"
},
{
"accountName": "Root:Asset:Bank",
"value": {
"num": "-10000",
"denom": "100"
},
"…": "…"
}
],
"…": "…"
}
}
Request:
{"jsonrpc":"2.0","id":2,"method":"transaction.update","params":{"guid":"c0000000000000000000000000000007","changeSplits":[{"guid":"d000000000000000000000000000000f","value":"90.00"}]}}
Response:
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": 2002,
"message": "…",
"data": {
"imbalance": "…",
"currency": "…",
"help": "error/unbalanced"
}
}
}
Command line
red-ale exec --book book.gnucash --script - <<'EOF'
transaction.update {"guid":"c0000000000000000000000000000007","removeSplits":["d0000000000000000000000000000010"],"changeSplits":[{"guid":"d000000000000000000000000000000f","value":"100.00"}]}
transaction.update {"guid":"c0000000000000000000000000000007","changeSplits":[{"guid":"d000000000000000000000000000000f","value":"90.00"}]}
EOF
It fails with exit status 2, printing on standard error:
{
"error": {
"code": 2002,
"message": "…",
"data": {
"imbalance": "…",
"currency": "…",
"help": "error/unbalanced"
}
}
}
MCP tool call
Tool call:
{"name":"transaction.update","arguments":{"guid":"c0000000000000000000000000000007","removeSplits":["d0000000000000000000000000000010"],"changeSplits":[{"guid":"d000000000000000000000000000000f","value":"100.00"}]}}
Result:
{
"structuredContent": {
"balanced": true,
"splits": [
{
"accountName": "Root:Expenses",
"value": {
"num": "10000",
"denom": "100"
},
"…": "…"
},
{
"accountName": "Root:Asset:Bank",
"value": {
"num": "-10000",
"denom": "100"
},
"…": "…"
}
],
"…": "…"
}
}
Tool call:
{"name":"transaction.update","arguments":{"guid":"c0000000000000000000000000000007","changeSplits":[{"guid":"d000000000000000000000000000000f","value":"90.00"}]}}
Result:
{
"isError": true,
"structuredContent": {
"error": {
"code": 2002,
"name": "unbalanced",
"message": "…",
"data": {
"imbalance": "…",
"currency": "…",
"help": "error/unbalanced"
}
}
}
}