One of the issues I have with current budgeting tools is that they are very inflexible. They work in terms of dollars per month in an allocated category but we all know that in real-life it’s very hard to allocate the same set number of dollars to each category every month. There are lots of problems with this approach
Because of those issues, I have been playing with the idea of a different type of budgeting, based on
Since the budget tools available are not ideal for this scenario, I decided to create my own budgeting tool and use Klutch’s API to automate that process.
This article assumes that you have the following setup:
Also, for article’s sake we are going to assume that all income is automatically loaded into your Klutch card (in this case we are using the Klutch Spend Card) by using Klutch’s “Transfer from your bank” feature.
Let’s start with a simple Node.js template to call our APIs and authenticate with the Klutch API.
We use “createSessionToken” to create a token for this session.
import dotenv from 'dotenv';
dotenv.config();
const ENDPOINT = process.env.KLUTCH_ENDPOINT;
const CLIENT_ID = process.env.KLUTCH_CLIENT_ID;
const SECRET_KEY = process.env.KLUTCH_SECRET_KEY;
async function gql(query, variables, token) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"content-type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {})
},
body: JSON.stringify({ query, variables })
});
const json = await res.json();
if (json.errors) throw new Error(JSON.stringify(json.errors));
return json.data;
}
async function main() {
const { createSessionToken } = await gql(
`mutation($clientId:String,$secretKey:String){
createSessionToken(clientId:$clientId, secretKey:$secretKey)
}`,
{ clientId: CLIENT_ID, secretKey: SECRET_KEY }
);
const token = createSessionToken;
const categories = await getCategories(token)
console.log("categories", categories)
}
main().catch(err => {
console.error("Error", err.message);
process.exit(1);
});
Your Klutch account already comes with pre-defined categories, but we can always add custom ones we want to use to define our budgets.
Let’s view our current categories, to do that, we query “transactionCategories”
async function getCategories(token) {
const {transactionCategories} = await gql(`
query {
transactionCategories {
id
name
mccs
}
}
`, null, token)
return transactionCategories
}
This call will return our current categories (we omitted the MCCs for clarity)
[
{ id: '114279a7-fcb3-4b60-a3f3-e7bc29d4f1f5', name: 'FITNESS' },
{ id: '315234af-f9bd-4403-a33e-d705373bfdd5', name: 'EDUCATION' },
{ id: '3142bba0-86b3-461f-a312-87e70e89f795', name: 'GIVING' },
{ id: '4122d2a0-b5be-11ec-a3cc-07604f65f135', name: 'CLOTHING' },
{ id: '4122d6a9-b5be-11ec-a3cc-07604f65f135', name: 'FOOD' },
{ id: '4122d7a6-b5be-11ec-a3cc-07604f65f135', name: 'FUN' },
{ id: '4122d7ad-b5be-11ec-a3cc-07604f65f135', name: 'GIFTS' },
{ id: '4122d8ab-b5be-11ec-a3cc-07604f65f135', name: 'HOME' },
{ id: '4122d8ad-b5be-11ec-a3cc-07604f65f135', name: 'PETS' },
{ id: '4122d9ad-b5be-11ec-a3cc-07604f65f135', name: 'TRANSPORT' },
{ id: '4122daa6-b5be-11ec-a3cc-07604f65f135', name: 'TRAVEL' },
{ id: '4192dcaa-04b8-4842-b368-67b94897f0a5', name: 'MISCELLANEOUS' },
{ id: '71927ba1-beb1-410a-b3cd-e7add410fac5', name: 'UTILITIES' },
{ id: '71022eae-6ebb-4141-830b-b7e33174fed5', name: 'KIDS' },
{ id: '71f20daf-46b4-4e46-83fd-a787db4cf325', name: 'DRINKS' },
{ id: '81b2e4a4-57b2-4db5-b337-d71f23ecf225', name: 'CAR' },
{ id: '9182f0a5-64b3-4d35-8343-b7b8562cf3d5', name: 'FINANCIAL ' },
{ id: 'a1b21cac-ccb0-4bf9-b35c-d79b6cdef5c5', name: 'MEDICAL' },
{ id: 'a132f1a4-f8b4-4013-83fd-3719b135fa95', name: 'FUN MONEY' },
{ id: 'a1c295a4-57b8-4489-9300-17a91764fdb5', name: 'HOTELS' },
{ id: 'c10203a2-edb4-489e-93c1-37433c18fdd5', name: 'GROCERIES' },
{ id: 'c1b2ffa3-b2b9-4a42-93b0-17c436e3f245', name: 'PAYMENT' },
{ id: 'd1f286a9-8cb6-4fff-83d8-27256f5ff455', name: 'APP' },
{ id: 'e14201a9-74ba-442d-b35a-879824dff8c5', name: 'FLIGHTS' },
{ id: 'e192ffa8-28b5-47a9-839a-f7ec530bfdb5', name: 'RESTAURANTS' },
{ id: 'f11272a9-bfb4-4a8b-8355-d7cb6da7f145', name: 'SHOPPING' },
{ id: 'f16218a1-cdb3-4b09-83ff-975640abfc65', name: 'WELLNESS' },
{ id: 'f18226ab-d6b2-4b80-a3aa-27f9fc4dfb95', name: 'BUSINESS' }
]
We can always add a new category by using “createTransactionCategory” later if we need to.
We will create our budget based on the current spend we have for each category. The idea is simple, we want our current budget to be proportional to what we spent last month based on the payments we had this month. We will also allocate up to a 10% increase on categories and create a rule to decline transactions exceeding that amount during the month.
Let’s first check our spend per category by using the “groupTransactions” API.
async function getSpendPerCategory(token) {
const now = new Date();
const startDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
const endDate = now.toISOString();
const queryVars = {
filter: {
transactionStatus: ["PENDING", "SETTLED"],
transactionTypes: ["CHARGE"],
startDate,
endDate
},
groupByProperty: "CATEGORY",
operation: "SUM"
}
const {groupTransactions} = await gql(`
query($filter: TransactionFilter, $groupByProperty: TransactionGroupByProperty, $operation: GroupByOperation) {
groupTransactions(filter: $filter, groupByProperty: $groupByProperty, operation: $operation) {
key
value
}
}
`, queryVars, token)
return groupTransactions
}
This call returns the sum of transactions per categories:
[
{ key: '4192dcaa-04b8-4842-b368-67b94897f0a5', value: 10.77 },
{ key: '81b2e4a4-57b2-4db5-b337-d71f23ecf225', value: 12.63 },
{ key: 'c10203a2-edb4-489e-93c1-37433c18fdd5', value: 28.04 },
{ key: 'f11272a9-bfb4-4a8b-8355-d7cb6da7f145', value: 63.81 },
{ key: '4122d8ab-b5be-11ec-a3cc-07604f65f135', value: 138.94 },
{ key: 'a132f1a4-f8b4-4013-83fd-3719b135fa95', value: 196.3 },
{ key: 'a1c295a4-57b8-4489-9300-17a91764fdb5', value: 334.75 },
{ key: 'f16218a1-cdb3-4b09-83ff-975640abfc65', value: 637 },
{ key: 'e192ffa8-28b5-47a9-839a-f7ec530bfdb5', value: 1235.63 },
{ key: '315234af-f9bd-4403-a33e-d705373bfdd5', value: 1943.99 }
]
To complete our budget construction, we also need last month's income.
We can use the “sumTransactions” mutation to query that:
async function getPaymentLastMonth(token) {
const now = new Date();
const startDate = new Date(now.getFullYear(),now.getMonth() - 1, 1).toISOString();
const endDate = new Date(now.getFullYear(), now.getMonth(), 1).toISOString();
const queryVars = {
filter: {
transactionStatus: ["SETTLED"],
transactionTypes: ["PAYMENT"],
startDate,
endDate,
}
};
const {sumTransactions} = await gql(`
query($filter: TransactionFilter) {
sumTransactions(filter: $filter)
}`, queryVars,token);
return sumTransactions * -1
}
This call returns the total payments we had last month:
4851.31
Going back to our main function, we can now retrieve the spend and payments we need and then create our budget based on the income we had last month:
First, let’s calculate the percentage of spending per category:
function calculateSpendPercentages(spendPerCategories) {
const total = spendPerCategories.reduce((sum, s) => sum + s.value, 0);
if (total === 0) return Object.fromEntries(spendPerCategories.map(s => [s.key, 0]));
return Object.fromEntries(
spendPerCategories.map(s => [s.key, s.value / total])
);
}
Returns:
{
"4192dcaa-04b8-4842-b368-67b94897f0a5": 0.0023403580291447373,
"81b2e4a4-57b2-4db5-b337-d71f23ecf225": 0.0027445424241502353,
"c10203a2-edb4-489e-93c1-37433c18fdd5": 0.006093188406426967,
"f11272a9-bfb4-4a8b-8355-d7cb6da7f145": 0.013866132389946675,
"4122d8ab-b5be-11ec-a3cc-07604f65f135": 0.030192139700034336,
"a132f1a4-f8b4-4013-83fd-3719b135fa95": 0.04265666491375227,
"a1c295a4-57b8-4489-9300-17a91764fdb5": 0.07274232592908085,
"f16218a1-cdb3-4b09-83ff-975640abfc65": 0.13842229011747426,
"e192ffa8-28b5-47a9-839a-f7ec530bfdb5": 0.2685066473121738,
"315234af-f9bd-4403-a33e-d705373bfdd5": 0.42243571077781594
}
The next step is to assign the budget based on our income from last month:
function attributePaymentsToCategories(categories, spendPercentages, paymentsLastMonth) {
return categories.map(c => ({
name: c.name,
value: (spendPercentages[c.id] ?? 0) * paymentsLastMonth
}));
}
Returns
[
{ id: '315234af-f9bd-4403-a33e-d705373bfdd5', value: 2049.366588053526 },
{ id: '4122d8ab-b5be-11ec-a3cc-07604f65f135', value: 146.47142924817356 },
{ id: '4122d9ad-b5be-11ec-a3cc-07604f65f135', value: 206.9407050627355 },
{ id: '4192dcaa-04b8-4842-b368-67b94897f0a5', value: 11.353802310370154 },
{ id: '81b2e4a4-57b2-4db5-b337-d71f23ecf225', value: 13.314626107704276 },
{ id: 'a1c295a4-57b8-4489-9300-17a91764fdb5', value: 352.8955732030092 },
{ id: 'c10203a2-edb4-489e-93c1-37433c18fdd5', value: 29.559945847983204 },
{ id: 'e192ffa8-28b5-47a9-839a-f7ec530bfdb5', value: 1302.6089831720217 },
{ id: 'f11272a9-bfb4-4a8b-8355-d7cb6da7f145', value: 67.26890672467219 },
{ id: 'f16218a1-cdb3-4b09-83ff-975640abfc65', value: 671.529440269804 }
]
Now that we have our target budget, we need to create transaction rules that will decline transactions when they exceed 110% of our budget for this month.
We will use the createTransactionRule mutation to create the rules.
async function createTransactionRule(budget, categories, token) {
const cat = categories.find(c => c.id == budget.id)
const queryVar = {
name: `diff_budget_${budget.id}`,
displayName: `Budget for ${budget.id}`,
cards: [], //all cards
spec: {
specType: "AccumulatingOverPeriodTransactionRule",
period: "MONTH",
amount: budget.value * 1.10,
filters: [
{
field: "MCC",
operator: "CONTAINS",
value: cat.mccs
}
]
}
}
const {createTransactionRule} = await gql(`
mutation($name: String, $displayName: String, $cardIds: [String], $spec: TransactionRuleSpecInput) {
createTransactionRule(name: $name, displayName: $displayName, cardIds: $cardIds, spec: $spec) {
id
name
}
}`, queryVar, token)
return createTransactionRule
}
Once that is run, a new transaction rule will be in effect that will decline transactions over our budget. We can always authorize the transaction if needed by replying 'YES' to the SMS message that Klutch sends when a transaction rule is triggered.
Our updated main function:
async function main() {
const { createSessionToken } = await gql(
`mutation($clientId:String,$secretKey:String){
createSessionToken(clientId:$clientId, secretKey:$secretKey)
}`,
{ clientId: CLIENT_ID, secretKey: SECRET_KEY }
);
const token = createSessionToken;
const categories = await getCategories(token);
const spendPerCategories = await getSpendPerCategory(token);
const paymentsLastMonth = await getPaymentLastMonth(token)
const spendPercentages = calculateSpendPercentages(spendPerCategories);
const attributedPayments = attributePaymentsToCategories(categories, spendPercentages, paymentsLastMonth);
for (const budget of attributedPayments) {
createTransactionRule(budget, categories, token)
}
}
This concludes this tutorial, but this is just a small sample of what can be accomplished with Klutch. As a next step, think about scheduling this call to run every day or adjusting it to take into account end of year expenses and other cyclical expenses or using a more sophisticated way to calculate your budgeting.
By leveraging the Klutch API, you can build a sophisticated differential budgeting tool that moves beyond static limits. This approach provides greater flexibility and real-time insights into spending patterns, empowering users to make more informed financial decisions
You can check out the entire source code for this tutorial on Github
Also don't hesitate to reach to our support (even for technical questions) if you need help.
Klutch grew from our belief that people should demand more from technology. So when we noticed a critical need for more control and flexibility in how people make payments, we went to work pioneering a framework that delivered just that.