Script to scrape Sofi Credit Card transactions into your Tiller Spreadsheet

Per Is SoFi Credit Card supported? , Sofi Credit Cards aren’t yet supported for automatic importing.

Wrote a little script that you can run in your console to get a .csv of transactions that you can paste into you spreadsheet.

  1. Open console from the Sofi Credit Card transactions page.

    • Windows – Ctrl + Shift + J.
    • Mac – Cmd + Opt +J.
  2. Copy this script into the console, but replace variables like getUntilDate at the top to match your needs.

Press here to see script
/*
 * Run on your Sofi Credit Card transactions page to scrape transactions into a CSV that can be copied into your Tiller Spreadsheet's "Transactions" page\
 
 You may need to scroll to bottom and load more to get older transactions
 */

// Script will gather transactions from today until this date in Month-Day-Year format
const getUntilDate = "12/06/2023"
// Whatever you want to show up for "Account #"
const accountNumber = "xxxx1234"
// Sofi account name (name you want to use under "Account" in transaction)
const account = "Credit Card"
// Whatever you want to show up for "Institution"
const institution = "SoFi"

const untilDate = new Date(getUntilDate)
const today = new Date()

for (const h3 of document.querySelectorAll("h3")) {
    if (h3.textContent.trim() === "Transactions") {
        const txContainer = h3.parentNode.parentNode.children[1]
        // Pending, pending tx list, posted tx list
        if (txContainer.children.length === 3) {
            getTransactionsFromDiv(txContainer.children[2])
        }
    }
}

function getTransactionsFromDiv(parent) {
    const transactions = []
    for (const child of parent.children) {
        // Skip anything that isn't a trx list container
        if (child?.children?.[0].nodeName !== "DIV") {
            continue;
        }
        // Loop over trx list
        for (const item of child?.children) {
            // Skip anything that isn't a trx
            if (item?.children?.[0]?.children?.[0].nodeName !== "BUTTON") {
                continue;
            }
            const itemWrapper = item?.children?.[0]?.children?.[0]?.children?.[0]?.children?.[0]?.children?.[0]?.children
            const descAmountContainer = itemWrapper?.[0]
            const trxDate = new Date(itemWrapper?.[1]?.textContent)
            if (trxDate.getTime() < untilDate.getTime()) {
                continue;
            }
            const description = descAmountContainer?.children?.[0]?.textContent
            const amount = descAmountContainer?.children?.[1]?.textContent.replaceAll("$", "").replaceAll(",", "")
            const month = `${trxDate.getMonth() + 1}/1/${getShortYear(trxDate)}`
            const trxMonday = getMonday(trxDate)
            const week = `${trxMonday.getMonth() + 1}/${trxMonday.getDate()}/${getShortYear(trxMonday)}`
            const dateAdded = `${today.getMonth() + 1}/${today.getDate()}/${getShortYear(today)}`
            transactions.push([
                `${trxDate.getMonth() + 1}/${trxDate.getDate()}/${trxDate.getFullYear()}`,
                description,
                "", // Category
                amount,
                account,
                accountNumber,
                institution,
                month,
                week,
                "", // Transaction ID. You can modify this script to expand each trx and get the number, but this isn't usefulf for me
                "", // Account ID (used internally by Tiller)
                "", // Check Number
                description,
                dateAdded
            ])
        }
    }
    console.log(arrayToCSV(transactions))
}


function getMonday(d) {
    d = new Date(d);
    var day = d.getDay(),
        diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
    return new Date(d.setDate(diff));
}

function getShortYear(d) {
    return d.getFullYear().toString().substr(2)
}

function arrayToCSV(arr, delimiter = ',') {
    return arr.map(v => v.map(x => `"${x}"`).join(delimiter)).join('\n');
}
  1. Copy the comma separated values provided from the script

  2. Add enough rows to your Transactions spreadsheet (can do this at the end of the spreadsheet)

  3. Paste values in Transactions spreadsheet

  4. With each pasted row still highlighted, go to “Data” → “Split Text to Columns” (in Google Sheets)

  5. Enjoy having your transactions migrated without having to manually input each one!

The trouble with this solution is if they make changes to the page it will break it. Hopefully, by then we can just use the auto importer.

If you don’t like the idea of running a script in your console, or if this breaks and you’d like another solution; this Gist has instructions for using the .pdf summary you get from Sofi to scrape transactions.

This is cool, @ebonsignori. SoFi doesn’t offer a CSV export?
One thing that is great about solutions like this is that they can be readily adapted to other institutions and workflows.

Thanks for sharing!