ARTICLES

Building a differential budgeting tool using Klutch’s API

Use Klutch API to create your own budget

Introduction to Differential Budgeting

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

  • Not every month is equal: Some are longer than others, summer months and winter months have different needs, etc.
  • Income is not necessarily the same – while most people have a salary and a predictable income flow, other people’s income varies greatly based on how much they work (such as hourly-workers) or commission
  • Life happens – and we need to use money we allocated for one category to cover a different category.

Because of those issues, I have been playing with the idea of a different type of budgeting, based on 

  • Percentage of available income – how much of my income I want to budget for each category
  • Differential in spending – How much more I am willing to spend vs what I have spent in the past.

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.

Prerequisites:

This article assumes that you have the following setup:

  • A Klutch account and access to the Klutch API
  • Basic understanding of Node.js, GraphQL and JSON

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.

Authentication:

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);
});

Define the categories:

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.

Current Spend

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

Creating our budget for this month

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 }
]

Creating our transaction rules

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)
  }
}

Conclusion and Next step

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.

About klutch

We’re on a mission to make your life better, one breakthrough app at a time.

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.

ABOUT KLUTCH
Renato steinberg - ceo
2:09
A portrait of Renato Steinberg