Overview

Categorizing transactions is the process of assigning accounting ledgers to real-world transactions. It is a core accounting process, and ensures that a user’s accounting entries accurately reflect their business.

Teal uses a customizable pipeline to automate transaction categorization, aiming to reduce the need for manual input from your users. Sometimes if our pipeline does not have enough information to categorize a transaction, it will be categorized into either the “Uncategorized Cash Inflow” or “Uncategorized Cash Outflow” ledger.

Configure your platform’s auto-categorizer in the developer portal.


Auto-categorization pipeline

Auto-categorizer pipeline

New transactions are sent through a sequence of rules-based steps, where each step attempts to match it to a ledger in the Instance’s chart of accounts.

We have four steps in our pipeline:

  1. Transfers between accounts
  2. Similarity checker
  3. Categorization rules
  4. AI Categorization

If one of these steps selects a ledger from the instance’s chart of accounts, the transaction will not be checked against the later rules. For example, transactions identified with the Transfer Between Accounts step, it will not be tested against platform rules or the similarity checker.


1. Transfers between accounts

The transfers between accounts step recognizes when a transaction is a transfer of funds between two financial accounts.

This works by looking for a transaction in another financial ledger that is of an equal and opposite amount within a 7 day window. If such a transaction exists, it assumes that these are matching transfers and creates two journal entries, one for the cash inflow and one for the outflow.

This step can only identify matching transactions after both the cash inflow and outflow exist in the system.

If you or your user know that a cash outflow is a transfer between accounts before the corresponding inflow has been received, you can manually categorize the line entry to the Transfers Between Accounts ledger — a special ledger that exists in all chart of accounts templates — and the system will automatically categorize the corresponding inflow when it arrives.


2. Similarity checker

The similarity checker looks for transactions that an Instance has manually categorized with a similar description and applies the same category.

Over time this step becomes more effective as an Instance categorizes more of their transactions, improving the overall efficiency of the auto-categorizer.


3. Categorization rules

Categorization rules enable instances and platforms to automatically categorize a transaction.

For platforms, this is useful if you are familiar with your customers’ spending and can help reduce the amount of time they spend manually categorizing transactions. For example, you might create a platform rule that puts every transaction with the description “UNITED AIRLINES” into the “Travel Expenses” ledger.

Instances may use categorization rules to handle specific cases where a platform’s rules might not make sense given their chart of accounts. For example, if there is a platform-level rule to categorize “UNITED AIRLINES” transactions as “Travel Expenses”, but the Instance wants it categorized into a “Flights Expenses” sub-ledger, they could create a rule to do so.

Categorization rules check transactions using an expression and priority. If a transaction matches multiple rules, the one with higher priority is selected.

Categorization rules set at the platform level will apply to every transaction, regardless of instance, on your platform.

Instance-level rules will always take priority over Platform-level rules.

See Crafting categorization rules below to learn how to write categorization rules. Platforms rules can be managed in the developer portal or via the Platform Categorization Rules endpoints. Instance rules can only be managed via the Instance Categorization Rules endpoints.


4. AI Categorization

This categorizer uses an LLM model to select a ledger based on the information contained in the Transaction object. It can sometimes take up to 5 minutes for for this categorizer to run.

To prioritize the end-user’s experience, a transaction can be manually categorized before this step finishes running. Manually categorizing the transaction stops this step.


Uncategorized transactions

Transactions can lack sufficient context or data and cannot be auto-categorized — for example, a bank transaction which is labelled “Payment”.

When a transactions goes through all the categorizers but does not find a match, it is categorized as either Uncategorized Cash Inflow or Uncategorized Cash Outflow. These special ledgers are hardcoded into each chart of accounts template.

In order for books to be balanced, it is necessary to create a user experience to allow users to manually categorize transactions. For steps on how to build an interface like this, see reviewing transactions.


Notes

Crafting categorization rules

A rule consists of three components:

  • Expression: A conditional statement that is evaluated against each transaction to determine whether it satisfies the criteria of the rule.
  • Priority: If multiple rules match a transaction, the rule with the highest priority will be implemented first.
  • Destination Ledger: This is the name of the ledger into which the transaction will be categorized if the rule conditions are met. If the specified ledger name doesn’t exist in an instance’s chart of accounts, the system will disregard the rule.

Expressions

Expressions are written by using one or a combination of possible patterns:

  • The match(pattern, input) function, which compares a pattern, which can be text, numbers, booleans, or dates, with an attribute of the Transaction object as the input.
  • Direct comparison of the Transaction object using operators and literals.

The Transaction object

The entire Transaction object is accessed via the symbol t and reference specific fields using ., for example, t.description or t.amount.

{
    {
      "amount": 100,
      "datetime": "2022-01-01T00:00:00Z",
      "description": "Shell Gas Bar 4124",
      "id": "t_9237232",
      "metadata": {
        // ...
      },
      "posted_status": "posted",
      "review_status": "reviewed"
    }
}

If metadata was added to the Transaction, you can reference it through t.metadata. See submitting transactions for more information on adding metadata to your transactions.

Operators and literals

Expressions may include the following operators and literals:

Using match()

Use match(pattern, input) to check if an attribute of the Transaction object, input, matches an arbitrary rule, pattern. The pattern must be a string and can contain regular expression syntax which we recommend to write complex rules.

You can learn more about regular expressions and their syntax here.

Examples

Payments to a specific vendor

A rule that checks if the Transaction’s description contains “Slack”, preceeded by any number of any character:

match(".*Slack", t.description)

will match

{
    "t": {
        "description": "Abc 7890Slack xYz"
        // ...
    }
}
Payments to a specific merchant over a given threshold amount

A rule that applies to any transaction where the Transaction description is exactly “Chevron” and the amount is more than $50:

match("Chevron", t.description) and t.amount > 50

will match

{
    "t": {
        "description": "Chevron",
        "amount": 60
        // ...
    }
}

but will not match

{
    "t": {
        "description": "abc Chevron", // false
        "amount": 60 // true
        // ...
    }
}

or

{
    "t": {
        "description": "Chevron", // true
        "amount": 40 // false
        // ...
    }
}
Transactions with certain metadata

A rule that looks for transactions with a counterparty attribute in the metadata set to “Sophie’s Contracting LLC”:

t.metadata.counterparty == "Sophie's Contracting LLC"

will match

{
    "t": {
        "metadata": {
            "counterparty": "Sophie's Contracting LLC"
        }
        // ...
    }
}

Further reading & guides