How to Create a Mint Import CSV of Transactions Using Python

Trying to upload transactions to Mint using a CSV and Python? Find the source code and examples on Github.

Why You Might Need to Add Transactions into Mint

To create a budget you are going to want data to base spending habits on. While mint.com allows for up to a 3-month auto-import, it is not guaranteed. This also neglects periodic purchases like new brakes or tires for your car.

The whole reason to have Mint is to be able to see all the data in one place. All in all, let’s face it: The software’s “from 30 to 90 days” just doesn’t cut it for new users.

My Search for Tools that do Mint Import Transactions

Having worked in technology for as long as I have, I knew I could not be the only one with this problem. So I immediately went on Google to see if there was anyone else who had solved this issue for me.

Low and behold, I did find a YouTube video of a proof of concept approach for automating manual transactions with a plug for their premium product. There was no code available though, so I would have to reverse engineer that myself.

Not wanting to totally start from scratch, I figured I’d check out GitHub too. I stumbled upon some similar code specifically for the UK locale and date formats. It was clearly written by someone else like me who had a one-off problem and decided to solve it themselves.

The code did lack some features and process clarity that I needed, so I figured I’d write my own version with the documentation where other people could easily extend or augment it for themselves in the future.

My Python 3 Import CSV Tool

First, I had to pick a language to write this tool in. At the time of writing this article, I have had professional experience in Java, Javascript, PHP, Ruby, Python, LUA, and Bash.

I chose Python for the ease of use of an interpreted language (one that is compiled on the fly by the machine, not the user) and the ease of PIP for extending functionality for things like CSVs.

Python Package Dependencies

We are going to need the following Python packages for our script. For those not familiar with installing packages via PIP, we have included instructions in the import.py documentation.

"""
#################################
Pre-requisites needed
#################################
If you are missing any of the following you can install with:
	pip install $name
	Example: pip install csv 
OR if you are using pip3
	pip3 install $name 
	Example: pip3 install csv 
"""

import csv
import datetime
import os
import random
import requests
import time
import urllib.parse
  • CSV – used to parse the input file
  • datetime – used to manipulate date strings and objects
  • os – used to interact with our local machine
  • random – used to implement random numbers
  • requests – used to submit HTTP requests (note we are actually just using curl right now)
  • time – used to interact with time strings and objects
  • urllib – used to HTTP encode strings so they can be submitted over POST and form data

Process to Importing Transactions

  1. Upload CSV data to python
  2. Process date for correct format and HTTP encode the result
  3. Process merchant text for HTTP encode
  4. Process categories and change your bank’s category name into a Mint category ID (limited in scope based on the categories I needed when I wrote this)
  5. Process amount for positive or negative value, indicating the transaction data is income or an expense
  6. Create CURL string post request
  7. Send POST Request to Mint as new transaction data.
  8. Force Randomized Wait Time before starting next request

Setting Up a Mint CSV Import Client

Because there is no public Mint API, we are going to need to find a way to mimic manual transaction adds using the UI’s form post.

William Lorfing (Intuit Developer Group)3 years ago

There are no APIs to access Mint data.

Mint HelpDesk as of 05/04/20

To do this we are going to need to use your browsers DevTools:

"""
#################################
Mint Client Credentials 
#################################
You will need the tags, cookie, and token to simulate a UI form submission. You can get these by opening developer tools > network analysis tab and doing 
a test submission in mint.com. From there look for the post request to "updateTransaction.xevent" and grab the credentials from the header and body
"""
account = 'XXXXXXX' # grab from POST request form body in devtools
tag1 = 'tagXXXXXX' # in form of tagXXXXXXX
tag2 = 'tagXXXXXXX' # in form of tagXXXXXXX
tag3 = 'tagXXXXXXX' # in form of tagXXXXXXX
cookie = 'XXXXXXX' # grab from POST request header in devtools 
referrer = 'XXXXXXX' # grab from POST request header in devtools 
token = 'XXXXXXX' # grab from POST request form body in devtools
  1. Go to Mint and login
  2. Go to transactions and click “+transaction”
  3. Open your DevTools (F12 on Chrome) and go to Network Tab
    1. Chrome DevTools - Network
  4. Submit a TEST transaction for $0.01, the network analysis tab will record the POST submission under “updateTransaction.xevent”
    1. Submit Mint Transaction
  5. Click on “updateTransaction.xevent” and you can see the headers of your request. Specifically, you will want the “request headers” for the cookie and the “form data” for things like tags and your token.

Setting Up a Bank’s Transactions CSV

Each bank will have a different way to get your transactions as CSV data. If your bank really doesn’t have this functionality,

  1. Get a new bank 😉
  2. Reach out to support as ask for a CSV file
  3. If they won’t do that, you can manually extract and copy the data into a spreadsheet, transform it into CSV format, then load it in (this is beyond the scope of this article, but we’ll have an article on scraping and text transformations later).

**STRONG RECOMMENDATION**
If you are uploading thousands of transactions like I did, you are going to want to sort your CSV by transaction date. This will allow you to retry from where it left off in the event of a failure.

What is important is understanding the format that the program expects. My script is based on a Chase.com CSVs export. If your bank uses a different format, you can adjust the column maps OR scrub it to match.

To those not familiar with reading Python, row[X] means the item X from left to right of that row starting with number 0. In my script, the first column must be the date of the transactions, followed by post date, merchant memo, categoryID, typeID, and amount of transaction (signed +-).

	# Initialize Variables
	date = (row[0]) 
	postDate = (row[1])
	merchant = (row[2])
	catID = (row[3])
	typeID = (row[4])
	amount = (float(row[5]))

Adding Transaction Categories

One of the main areas I did not fully develop is in category mapping. Mint requires you to use categoryIDs and names defined by them, which means the ones from your bank will not work. To deal with this, I have implemented two functions:

# Category ID Mapping Function 
	def category_id_switch(import_category):

category_id_switch maps the Chase named categories to Mint category ids. To make this match your bank’s categories, you just change the keys like “Credit Card Payment” to whatever your bank calls it. To add additional category support, I have included Mint’s category menu in html format as of 05/04/20 where you can grab the category names and ids from the markup.

# Category NAME Mapping Function 
	def category_name_switch(mint_id):

def category_name_switch maps Mint category ids to their Mint names. To add additional category support I have included Mint’s category menu in html format as of 05/04/20 where you can grab the category names and ids from the markup.

There was only one special case in my 1,800 transactions, and that is when Chase’s type = “payment”. In these scenarios, we had to handle them as catID 2101 in Mint. If your bank uses a different payment category, you can either comment this code out or map it to a category of your choosing.

# typeID payment overrides all categories 
	if typeID == "Payment":
		catID = '2101' # Since I was importing credit cards I have mine set to credit card payment. If you are doing bank accounts you many want to change this to payment general

Running CSV Import

Once you’ve got your client all setup and your category maps set all you need to do is run the following command from the directory your code and the CSV are in:

python3 import.py

OR if you are not using the python3 namespace

python import.py

That is it! You can sit back and watch the transactions add into your profile.

Summary
software image
Author Rating
1star1star1star1stargray
Aggregate Rating
5 based on 36 votes
Software Name
Mint CSV Importer
Operating System
MacOS, Linux, Windows with Cygwin
Software Category
Finance and Accounting
Price
USD 0.00
Landing Page
Share This:

24 thoughts on “How to Create a Mint Import CSV of Transactions Using Python”

  1. FYI, I was playing around with the “category menu in HTML format” you published to see if I could generate a complete list of categories and IDs. I noticed there are several categories with an ID beginning with “1963”. These categories are 7 digits long and have some oddly specific names. My guess is they’re custom categories you added to mint.

    I did manage to make a full list and added it into your import.py switcher. I’ll put it up on github when I get it working.

  2. Almost got this to work but not quite…the category name was always being set to “Uncategorized” because the catID was stringified before the lookup. Solved this by stringifying after setting category. The second problem is, the curls look successful (response 0) but I do not see the transactions appear in my account. The HTTP location shows ‘internalError.event’, but not sure what that means.

    HTTP/2 302
    date: Fri, 14 Aug 2020 06:53:53 GMT
    content-length: 0
    location: http://mint.intuit.com/internalError.event
    server: Mint App
    x-frame-options: SAMEORIGIN
    cache-control: no-cache, no-store, must-revalidate,
    expires: 0
    pragma: no-cache
    x-is-fdp-user: true
    x-mint-start: 1597388033155
    x-mint-end: 1597388033166
    content-language: en-US
    strict-transport-security: max-age=16000000; includeSubDomains; preload;
    x-xss-protection: : 1;mode=block
    x-content-type-options: : nosniff

    1. @christine HTTP/2 302 means you were redirected to there. Since this is mimic’ing front end UI not a real API my assumption would be your credentials expired and it was trying to bounce you somewhere else rather than 403 you. Obviously for security reasons do not post your token in here, will check to see if I can replicate this on my version today.

      A good way to check to see if this is the case is to just do a single curl using command line, if that works all the script does is duplicate that same request.

      1. I am getting same issue as well..Executing single curl command gives same issue as well. Also, I removed accountId from referrer query as mint seems to be not having that any more in its request and form body..Adding referrer to command results in same outcome..

        Here is my curl command (I removed Cookie and token :-))

        curl -i -s -k -X POST ‘https://mint.intuit.com/updateTransaction.xevent’ -H ‘Host: mint.intuit.com’ -H ‘User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36’ -H ‘Accept: */*’ -H ‘Accept-Language: en-US,en;q=0.5’ –compressed -H ‘X-Requested-With: XMLHttpRequest’ -H ‘Content-Type: application/x-www-form-urlencoded; charset=UTF-8’ -H ‘Referer: https://mint.intuit.com/transaction.event‘ -H ‘Cookie: ‘ -H ‘Connection: close’ –data ‘cashTxnType=on&mtCheckNo=&tag2917210=0&tag2917209=0&=0&task=txnadd&txnId=%3A0&mtType=cash&mtAccount=10866214&symbol=&note=&isInvestment=false&catId=20&category=Imported&merchant=Citi%3A%20AMAZON%20MKTPLACE%20PMTS%20AMZN.COM/&date=1/16/17&amount=-4.88&mtIsExpense=true&mtCashSplitPref=2&token=’

        HTTP/2 302
        date: Sat, 26 Sep 2020 12:43:07 GMT
        content-length: 0
        location: http://mint.intuit.com/internalError.event
        server: Mint App
        x-frame-options: SAMEORIGIN
        cache-control: no-cache, no-store, must-revalidate,
        expires: 0
        pragma: no-cache
        x-is-fdp-user: true
        x-mint-start: 1601124187536
        x-mint-end: 1601124187545
        content-language: en-US
        strict-transport-security: max-age=16000000; includeSubDomains; preload;
        x-xss-protection: : 1;mode=block
        x-content-type-options: : nosniff

    2. Hey there!

      Just wondering how you fixed the category issue/ if you could provide the code! I got the rest of the code working but am having the same issue as you, Uncategorized shows up in mint, and I’m not sure how to fix it from your response or where to look. Thanks in advance if you have it!

      1. If you are getting uncategorized there are 2 places that comes from

        1) is no category in your csv
        2) no category mapping in your python file

        Check line 307 and 291 in the current build. Basically it’s taking what is in your CSV, and then running a giant switch statement to turn it into an ID. If your catName is not in the switch it defaults to uncategorized. If csv has no category it defaults to uncategorized.

  3. I keep getting a 255 curl response. When I try and run a single curl statement from the terminal, I get “xxx is not recognized as an internal or external command” where xxx is a string from the cookie.

      1. Okay did quite a bit of testing and was able to reproduce the 255 error. Issue seems to be with the windows libcurl syntax vs the linux subsystem’s. I am planning to move this project off curl in the near future. But if you wanted to try and verify, you can copy the curl command in your browser’s network tab as curl for bash and run it using linux subsystem and it works. Do the same as curl for cmd and it fails with 255 (at least i does on my system).

        1. I’m also getting the 255 response. Hope you are able to come up with a new solve. Most of this is way over my head so definitely relying on you to create a new solution.

  4. I just installed and ran your latest code. All seems like it should work. I presume the cookie is as long as I’m seeing. Running on a mac. Any quick tips after checking that the session is alive?

    HTTP/2 500
    date: Mon, 02 Nov 2020 00:59:58 GMT
    content-length: 0
    server: Mint App
    strict-transport-security: max-age=16000000; includeSubDomains; preload;
    x-xss-protection: : 1;mode=block
    x-content-type-options: : nosniff

    Transaction Date: 01/15/2020
    Merchant Restaurants
    Category ID: 707
    Category Name: Uncategorized
    Amount: -00.01
    Expense: true
    CURL Request: curl -i -s -k -X POST ‘https://mint.intuit.com/updateTransaction.xevent’ -H ‘Host: mint.intuit.com’ -H ‘User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36’ -H ‘Accept: */*’ -H ‘Accept-Language: en-US,en;q=0.5’ –compressed -H ‘X-Requested-With: XMLHttpRequest’ -H ‘Content-Type: application/x-www-form-urlencoded; charset=UTF-8’ -H ‘Referer: https://mint.intuit.com/transaction.event?accountId=https://mint.intuit.com/transaction.event‘ -H ‘Cookie: ivid=XXX; ixp_ivid=XXX; ccpa=XXX; ivid_b=XXX; visitCount=XXX; tt_ipgeo=XXX; ivid_p=XXX; AMCV_969430F0543F253D0A4C98C6%40AdobeOrg=XXX; qbn.gauthid=XXX; qbn.agentid=XXX; qbn.uidp=XXX; qbn.parentid=50000003; userIdentifier=XXX; ius_at.enabled=true; ius_at=XXX; _exp_mintPN=10; MINTJSESSIONID=XXX; ROUTEID=XXX; mintPN=XX; currentClientType=XX; brandingOption=whitelabel; current-config-source=Back-end; userguid=XX; mintUserName=””; pseudonymID=XXX; _transactionIframeLocation=undefined’ -H ‘Connection: close’ –data ‘cashTxnType=on&mtCheckNo=&tag1303xxx=0&tag1303xxx=0&tag1303xxx=0&task=txnadd&txnId=%3A0&mtType=cash&mtAccount=XXX&symbol=&note=&isInvestment=false&catId=707&category=Uncategorized&merchant=Restaurants&date=01/15/2020&amount=-0.01&mtIsExpense=true&mtCashSplitPref=2&token=XXX’
    CURL Response: 0

  5. I got this script to run after re-configuring the header to match the Mint curl request that I copied from my browser’s network tab. Also, I was getting the ‘HTTP/1.1 302 Moved Temporarily’ response for many transactions, which I discovered was caused by special characters in my .csv data ‘Description’ column. Here’s what I fixed in the .csv to get the transactions to go through:
    > date format must be in mm/dd/yyyy
    > description:
    > remove where there are two spaces together (‘ ‘)
    > remove apostrophes (‘)
    > remove double percent signs (%%)
    > remove single percent signs (%)

    Here’s an example CURL which was successful:

    curl -i -s -k -X POST ‘https://mint.intuit.com/updateTransaction.xevent’ -H ‘User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:85.0) Gecko/20100101 Firefox/85.0’ -H ‘Accept: */*’ -H ‘Accept-Language: en-US,en;q=0.5’ –compressed -H ‘X-Requested-With: XMLHttpRequest’ -H ‘Content-Type: application/x-www-form-urlencoded; charset=UTF-8’ -H ‘Origin: https://mint.intuit.com‘ -H ‘Connection: keep-alive’ -H ‘Referer: https://mint.intuit.com/transaction.event‘ -H ‘Cookie: ‘ –data-raw ‘cashTxnType=on&mtCheckNo=&tag1263673=0&tag1263674=0&tag1263675=0&task=txnadd&txnId=%3A0&mtType=cash&mtAccount=&symbol=&note=&isInvestment=false&catId=20&category=&merchant=Check%20#%200000000107&date=04%2F10%2F2020&amount=-172.9&mtIsExpense=true&mtCashSplitPref=1&mtCashSplit=on&token=’

  6. Thought I’d leave this comment for others like me who have no idea what to do with the above but want to use it… it looks complicated but after poking around asking for internet help it was really easy! I just used a live USB of linux (in my case Linux Lite 3.8 on a crappy old netbook), copied the downloaded repository zip to the live linux, extracted, put in the parameters from Mint into import.py, copied over my csv to import, then in a terminal window from the folder with import.py entered “python3 import.py” and off it went, worked like a charm! I also was importing data exported from a Chase credit card so the categories and columns all were the same and I did this on 2/11/2021. Thanks!!!

  7. I’ve edited import.py… and tried just importing the test file import.csv

    (venv) ronb@mercury:~/Programs/mint-csv-import-master$ python3 import.csv
    File “import.csv”, line 1
    Transaction Date,Post Date,Description,Category,Type,Amount
    ^
    SyntaxError: invalid syntax

    Ideas where I could be going wrong?

  8. Nathaniel,

    Thanks a lot for making your code available. I had to port over 11,000 transactions saved up from my FinanceWorks account to Mint for years 1999 through 2018. I changed my CSV file a bit following your example file. I had to add a few categories to map FinanceWork categories to Mint categories on “def category_id_switch” function. This change made all the transactions to be categorized into Mint rather than being tagged as “Uncategorized”. While running your code, I frequently refreshed Mint page to make sure that the account stayed logged in. After about 6 hours, I had to leave the computer unattended for the night. Today I found that about 9,000 new transactions came through in my Mint account – although the python code seems to indicated that it has uploaded all of the 11,000 transactions. Probably my Mint account was logged out automatically in the night. I re-ran the code with the rest of the CSV file and with the new login credentials. While I do get CURL Response: 0, the Mint is not actually registering those transactions. Do you think Mint prevents accounts from accepting too many manual transactions? At any rate, thanks a lot for your post and codes, which have saved me a lot of time & money (MintImporter charges $20 for 200 transaction uploads).

    1. Hello Manoj,

      I do believe there is rate limiting on mint’s side, though it would be a bit painful on 11,000 transactions you could try to work with the random delay setting to side skirt that issue.

Leave a Reply

Your email address will not be published. Required fields are marked *