├── import.csv ├── import.py ├── readme.md ├── requirements.txt ├── sample form data 2020-05-02.txt ├── sample mint category menu 2020-05-02.txt └── sample request header 2020-05-02.txt /import.csv: -------------------------------------------------------------------------------- 1 | Transaction Date,Post Date,Description,Category,Type,Amount 2 | 07/22/2018,07/23/2018,NORTH PARK PRODUCE,Groceries,Sale,-1.04 3 | 07/22/2018,07/23/2018,LITTLE SHEEP MONGOLIAN,Food & Drink,Sale,-58.29 4 | -------------------------------------------------------------------------------- /import.py: -------------------------------------------------------------------------------- 1 | """ 2 | ################################# 3 | Pre-requisites needed 4 | ################################# 5 | 6 | If you are missing any of the following you can install with: 7 | 8 | pip install $name 9 | Example: pip install csv 10 | 11 | OR if you are using pip3 12 | 13 | pip3 install $name 14 | Example: pip3 install csv 15 | """ 16 | 17 | import csv 18 | import datetime 19 | import os 20 | import random 21 | import requests 22 | import time 23 | import urllib.parse 24 | 25 | """ 26 | ################################# 27 | Overview: 28 | ################################# 29 | 30 | Simulates bulk manual transaction adds to mint.com. Mint manual transactions are submitted as "cash transactions" which 31 | will mean it shows in your cash / all accounts transaction list. You cannot submit manual transactions against credit 32 | cards or other integrated bank accounts (even in Mint's UI this is not possible and ends up as cash transction). 33 | 34 | Approach Credits: 35 | Simulating manual transactions from UI is based on Nate H's proof of concept from https://www.youtube.com/watch?v=8AJ3g5JGmdU 36 | 37 | Python Credits: 38 | Credit to https://github.com/ukjimbow for his work on Mint imports for UK users in https://github.com/ukjimbow/mint-transactions 39 | 40 | Process Documentation: 41 | 1. Import CSV 42 | 2. Process date for correct format and HTTP encode result 43 | 3. Process merchant for HTTP encode 44 | 4. Process cateogories change your banks category name into a mint category ID (limited in scope based on the categories 45 | 5 needed when I wrote this) 46 | 6. Process amount for positive or negative value indicating income or expense 47 | 7. Send POST Request to mint as new transaction. 48 | 8. Force Randomized Wait Time before starting next request 49 | 50 | Future Development: 51 | 1. Replace curl command string generation with parametized curl class constructor 52 | 2. Add support for the rest of the manual transaction items 53 | 54 | """ 55 | 56 | """ 57 | ################################# 58 | Settings 59 | ################################# 60 | """ 61 | csv_name = 'import.csv' # name of csv you you want import to mint [string.csv] 62 | verbose_output = 1 # should verbose messages be printed [0,1] 63 | uk_to_us = 0 # do you need to change dates from UK to US format [0,1] 64 | min_wait = 0 # min wait time in seconds between requests, int[0-n] 65 | max_wait = 2 # max wait time in seconds between requests, int[0-n] 66 | 67 | """ 68 | ################################# 69 | Mint Client Credentials 70 | ################################# 71 | 72 | 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 73 | 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 74 | """ 75 | account = 'XXXXXXX' # grab from POST request form body in devtools 76 | tag1 = 'tagXXXXXX' # in form of tagXXXXXXX 77 | tag2 = 'tagXXXXXXX' # in form of tagXXXXXXX 78 | tag3 = 'tagXXXXXXX' # in form of tagXXXXXXX 79 | cookie = 'XXXXXXX' # grab from POST request header in devtools 80 | referrer = 'XXXXXXX' # grab from POST request header in devtools 81 | token = 'XXXXXXX' # grab from POST request form body in devtools 82 | 83 | """ 84 | ################################# 85 | Import CSV using the pythons csv reader 86 | ################################# 87 | """ 88 | csv_object = csv.reader(open(csv_name,'rU')) 89 | next(csv_object) 90 | 91 | for row in csv_object: 92 | 93 | # Initialize Variables 94 | date = (row[0]) 95 | postDate = (row[1]) 96 | merchant = (row[2]) 97 | catName = (row[3]) 98 | typeID = (row[4]) 99 | amount = (float(row[5])) 100 | expense = 'true' 101 | curl_input = 'Error: Did not Generate' 102 | curl_output = 'Error: Did not run' 103 | 104 | """ 105 | ################################# 106 | Process Date for format and HTTP Encode 107 | ################################# 108 | """ 109 | 110 | # Convert Commonwealth to US Date System 111 | if uk_to_us == 1: # based on setting 112 | dateconv = time.strptime(date,"%d/%m/%Y") # not needed for US to US 113 | date = (time.strftime("%m/%d/%Y",dateconv)) # converted new US date format from UK 114 | 115 | # Require "/" for date delimiter and HTTP Encode Character, supports "/", ".", "-" 116 | # We are not using url encode library here because we custom map other delimiters 117 | dateoutput = date.replace("/", "%2F") 118 | dateoutput = date.replace(".", "%2F") 119 | dateoutput = date.replace("-", "%2F") 120 | 121 | """ 122 | ################################# 123 | Process Merchant with HTTP Encode 124 | ################################# 125 | """ 126 | merchant = urllib.parse.quote(merchant) 127 | 128 | """ 129 | ################################# 130 | Process Categories 131 | ################################# 132 | 133 | Support is limited to the categories I needed at the time, if you need to map more you can. To get category ids: 134 | 1. Go to mint 135 | 2. Add a transactions 136 | 3. Right click "inspect-element" on the category you want 137 | 4. The ID is in the
  • item that encapsulates the a href 138 | 5. Add mapping here based on string match from your CSV to the catID you got from mint (following existing examples) 139 | """ 140 | 141 | # Category ID Mapping Function 142 | def category_id_switch(import_category): 143 | 144 | # Define mapping of import categories to : Mint Category IDs 145 | switcher={ 146 | #Chase categories - incomplete 147 | 'Gas':1401, 148 | 'Food & Drink':7, 149 | 'Groceries':701, 150 | 'Bills & Utilities':13, 151 | 'Shopping':2, 152 | 'Health & Wellness':5, 153 | 'Personal':4, 154 | 'Credit Card Payment':2101, 155 | 'Travel':15, 156 | 'Entertainment':1, 157 | 'Automotive':14, 158 | 'Education':10, 159 | 'Professional Services':17, 160 | 'Home':12, 161 | 'Fees & Adjustments':16, 162 | 'Gifts & Donations':8, 163 | # American Express Categories - Incomplete 164 | # American Express doesn't provide category information for payments, so I recommend manually changing those to "Payment" 165 | 'Merchandise & Supplies-Groceries':701, 166 | 'Transportation-Fuel':1401, 167 | 'Fees & Adjustments-Fees & Adjustments':16, 168 | 'Merchandise & Supplies-Wholesale Stores':2, 169 | 'Restaurant-Restaurant':707, 170 | 'Payment':2101, 171 | # The following categories are Mint categories. 172 | # Citi does not included categories in downloaded transactions so I added my own categories using the Mint categories. 173 | # These mappings make sure those categories don't get mapped to 'uncategorized:20' when they aren't found in the mappings 174 | # for the other banks above. 175 | # 176 | # If you want to change a category mapping, be mindful that some category names may be repeated because Mint uses 177 | # the same category name as another bank. 178 | 'Auto & Transport':14, 179 | 'Auto Insurance':1405, 180 | 'Auto Payment':1404, 181 | 'Gas & Fuel':1401, 182 | 'Parking':1402, 183 | 'Public Transportation':1406, 184 | 'Service & Parts':1403, 185 | 'Bills & Utilities':13, 186 | 'Home Phone':1302, 187 | 'Internet':1303, 188 | 'Mobile Phone':1304, 189 | 'Television':1301, 190 | 'Utilities':1306, 191 | 'Business Services':17, 192 | 'Advertising':1701, 193 | 'Legal':1705, 194 | 'Office Supplies':1702, 195 | 'Printing':1703, 196 | 'Shipping':1704, 197 | 'Education':10, 198 | 'Books & Supplies':1003, 199 | 'Student Loan':1002, 200 | 'Tuition':1001, 201 | 'Entertainment':1, 202 | 'Amusement':102, 203 | 'Arts':101, 204 | 'Movies & DVDs':104, 205 | 'Music':103, 206 | 'Newspapers & Magazines':105, 207 | 'Fees & Charges':16, 208 | 'ATM Fee':1605, 209 | 'Bank Fee':1606, 210 | 'Finance Charge':1604, 211 | 'Late Fee':1602, 212 | 'Service Fee':1601, 213 | 'Trade Commissions':1607, 214 | 'Financial':11, 215 | 'Financial Advisor':1105, 216 | 'Life Insurance':1102, 217 | 'Food & Dining':7, 218 | 'Alcohol & Bars':708, 219 | 'Coffee Shops':704, 220 | 'Fast Food':706, 221 | 'Groceries':701, 222 | 'Restaurants':707, 223 | 'Gifts & Donations':8, 224 | 'Charity':802, 225 | 'Gift':801, 226 | 'Health & Fitness':5, 227 | 'Dentist':501, 228 | 'Doctor':502, 229 | 'Eyecare':503, 230 | 'Gym':507, 231 | 'Health Insurance':506, 232 | 'Pharmacy':505, 233 | 'Sports':508, 234 | 'Home':12, 235 | 'Furnishings':1201, 236 | 'Home Improvement':1203, 237 | 'Home Insurance':1206, 238 | 'Home Services':1204, 239 | 'Home Supplies':1208, 240 | 'Lawn & Garden':1202, 241 | 'Mortgage & Rent':1207, 242 | 'Income':30, 243 | 'Bonus':3004, 244 | 'Interest Income':3005, 245 | 'Paycheck':3001, 246 | 'Reimbursement':3006, 247 | 'Rental Income':3007, 248 | 'Returned Purchase':3003, 249 | 'Kids':6, 250 | 'Allowance':610, 251 | 'Baby Supplies':611, 252 | 'Babysitter & Daycare':602, 253 | 'Child Support':603, 254 | 'Kids Activities':609, 255 | 'Toys':606, 256 | 'Misc Expenses':70, 257 | 'Personal Care':4, 258 | 'Hair':403, 259 | 'Laundry':406, 260 | 'Spa & Massage':404, 261 | 'Pets':9, 262 | 'Pet Food & Supplies':901, 263 | 'Pet Grooming':902, 264 | 'Veterinary':903, 265 | 'Shopping':2, 266 | 'Books':202, 267 | 'Clothing':201, 268 | 'Electronics & Software':204, 269 | 'Hobbies':206, 270 | 'Sporting Goods':207, 271 | 'Taxes':19, 272 | 'Federal Tax':1901, 273 | 'Local Tax':1903, 274 | 'Property Tax':1905, 275 | 'Sales Tax':1904, 276 | 'State Tax':1902, 277 | 'Transfer':21, 278 | 'Credit Card Payment':2101, 279 | 'Transfer for Cash Spending':2102, 280 | 'Travel':15, 281 | 'Air Travel':1501, 282 | 'Hotel':1502, 283 | 'Rental Car & Taxi':1503, 284 | 'Vacation':1504, 285 | 'Uncategorized':20, 286 | 'Cash & ATM':2001, 287 | 'Check':2002, 288 | 'Hide from Budgets & Trends':40, 289 | } 290 | # Get the mint category ID from the map 291 | return switcher.get(import_category,20) # For all other unmapped cases return uncategorized category "20" 292 | 293 | # typeID payment overrides all categories 294 | if typeID == "Payment": 295 | 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 296 | 297 | # if type is NOT payment then do a category check 298 | else: 299 | 300 | # if there IS no cat it is uncategorized 301 | if len(catName) == 0: 302 | catID = '20' # mint's uncategorized category 303 | 304 | # If there is a category check it against mapping 305 | else : 306 | # Use a switch since there may be MANY category maps 307 | catID = str(category_id_switch(catName)) 308 | 309 | 310 | # Set mint category name by looking up name in ID map 311 | category = catName 312 | category = urllib.parse.quote(category) 313 | 314 | """ 315 | ################################# 316 | Process Amount seeing if transaction is an expense or income. 317 | ################################# 318 | """ 319 | if amount < 0: 320 | expense = 'true' # when amount is less than 0 this is an expense, ie money left your account, ex like buying a sandwich. 321 | else: 322 | expense = 'false' # when amount is greater than 0 this is income, ie money went INTO your account, ex like a paycheck. 323 | amount = str(amount) # convert amount to string so it can be concatenated in POST request 324 | 325 | """ 326 | ################################# 327 | Build CURL POST Request 328 | TODO: Swap command string generation for parametized curl class 329 | ################################# 330 | """ 331 | 332 | # Break curl lines 333 | curl_line = " " 334 | 335 | # fragment curl command 336 | curl_command = "curl -i -s -k -X POST 'https://mint.intuit.com/updateTransaction.xevent'" + curl_line 337 | curl_host = "-H 'Host: mint.intuit.com'" + curl_line 338 | curl_user_agent = "-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'" + curl_line 339 | curl_accept = "-H 'Accept: */*'" + curl_line 340 | curl_accept_language = "-H 'Accept-Language: en-US,en;q=0.5'" + curl_line 341 | curl_compressed = "--compressed" + curl_line 342 | curl_x_requested_with = "-H 'X-Requested-With: XMLHttpRequest'" + curl_line 343 | curl_content_type = "-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8'" + curl_line 344 | curl_referer = "-H 'Referer: https://mint.intuit.com/transaction.event?accountId=" + referrer + "'" + curl_line 345 | curl_cookie = "-H 'Cookie: " + cookie + "'" + curl_line 346 | curl_connection = "-H 'Connection: close' " + curl_line 347 | curl_data = "--data" + curl_line 348 | 349 | # Fragment the curl form data 350 | form_p1 = "'cashTxnType=on&mtCheckNo=&" + tag1 + "=0&" + tag2 + "=0&" + tag3 + "=0&" 351 | form_p2 = "task=txnadd&txnId=%3A0&mtType=cash&mtAccount=" + account + "&symbol=¬e=&isInvestment=false&" 352 | form_p3 = "catId="+catID+"&category="+category+"&merchant="+merchant+"&date="+dateoutput+"&amount="+amount+"&mtIsExpense="+expense+"&mtCashSplitPref=1&mtCashSplit=on&" 353 | form_p4 = "token=" + token + "'" 354 | 355 | # Piece together curl form data 356 | curl_form = form_p1 + form_p2 + form_p3 + form_p4 357 | 358 | # Combine all curl fragments together into an entire curl command 359 | curl_input = curl_command + curl_host + curl_user_agent + curl_accept + curl_accept_language + curl_compressed + curl_x_requested_with + curl_content_type + curl_referer + curl_cookie + curl_connection + curl_data + curl_form 360 | 361 | """ 362 | ################################# 363 | Submit CURL POST Request 364 | ################################# 365 | """ 366 | curl_output = str(os.system(curl_input)) # use os sytem to run a curl request submitting the form post 367 | 368 | """ 369 | ################################# 370 | Verbose Output for Debug 371 | ################################# 372 | """ 373 | if verbose_output == 1: 374 | print ('Transaction Date:', dateoutput) # date of transaction 375 | print ('Merchant', merchant) # merchant Description 376 | print ('Category ID:', catID) # category of transaction 377 | print ('Category Name:', category) # category of transaction 378 | print ('Amount:', amount) # amount being processed 379 | print ('Expense:', expense) # in amount expense 380 | print ('CURL Request:', curl_input) # what was sent to mint 381 | print ('CURL Response:', curl_output) # what was returned from mint OR curl ERROR 382 | print ('\n\n==============\n') # new line break 383 | 384 | """ 385 | ################################# 386 | Force a random wait between 2 and 5 seconds per requests to simulate UI and avoid rate limiting 387 | ################################# 388 | """ 389 | time.sleep(random.randint(min_wait, max_wait)) 390 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | For a complete overview of how the Mint CSV Importer was developed check out - https://nathanielkam.com/import-transactions-to-mint-using-python/ 2 | 3 | # Run: 4 | Run the code by typing the following from the directory the code is located in 5 | python3 import.py 6 | 7 | ## Pre-requisites needed: 8 | 9 | csv 10 | datetime 11 | os 12 | random 13 | requests 14 | time 15 | urllib.parse 16 | 17 | Virtual Environment Setup (from app repo root) 18 | 1. Make sure you have venv on your system, using the following command based on your python version 19 | - python3 -m pip3 install virtualenv 20 | 2. Make sure you are in repo root \ 21 | - (where import.py and requirements.txt are) 22 | 3. Create a virtual environment 23 | - virtualenv venv 24 | 4. Turn on the virtual environment (these should work on both but depends on your version you may need to explicitly run the sh or bat file) 25 | - Mac / Linux in terminal or bash: venv/Scripts/activate 26 | - Windows in powershell: venv\Scripts\activate 27 | 5. Install Requirements 28 | - pip3 install -r requirements.txt 29 | 30 | 31 | ## Overview: ## 32 | Simulates bulk manual transaction adds to mint.com. Mint manual transactions are submitted as "cash transactions" which 33 | will mean it shows in your cash / all accounts transaction list. You cannot submit manual transactions against credit 34 | cards or other integrated bank accounts (even in Mint's UI this is not possible and ends up as cash transction). 35 | 36 | ## Approach: ## 37 | Simulating manual transactions from UI is based on Nate H's proof of concept from https://www.youtube.com/watch?v=8AJ3g5JGmdU 38 | 39 | ## Python: ## 40 | Credit to https://github.com/ukjimbow for his work on Mint imports for UK users in https://github.com/ukjimbow/mint-transactions 41 | 42 | ## Process: ## 43 | 1. Import CSV 44 | 2. Process date for correct format and HTTP encode result 45 | 3. Process merchant for HTTP encode 46 | 4. Process categories. Change your banks category name into a mint category ID (limited in scope based on the categories needed when I wrote this) 47 | 6. Process amount for positive or negative value indicating income or expense 48 | 7. Send POST Request to mint as new transaction. 49 | 8. Force Randomized Wait Time before starting next request 50 | 51 | ## Instructions: ## 52 | 1. Prepare your data to import using import.csv as an example 53 | 2. Edit import.py and replace the variables set to XXXXX's to values in your browser during a live Mint session 54 | - account is mtaccount and approximately 8 digits 55 | - tag1 is in form of tagXXXXXXX 56 | - tag2 is in form of tagXXXXXXX 57 | - tag3 is in form of tagXXXXXXX 58 | - cookie will be an apprimately 2000 character string 59 | - referrer is likely always 'https://mint.intuit.com/transaction.event' 60 | - token is approximately 50 characters 61 | 3. If you have custom categories, they need to go along others in function category_id_switch() 62 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | DateTime==4.3 2 | requests==2.24.0 3 | urllib3==1.25.10 4 | -------------------------------------------------------------------------------- /sample form data 2020-05-02.txt: -------------------------------------------------------------------------------- 1 | cashTxnType: on 2 | mtCheckNo: 3 | tagXXXXXXX: 0 4 | tagXXXXXXX: 0 5 | tagXXXXXXX: 0 6 | task: txnadd 7 | txnId: :0 8 | mtType: cash 9 | mtAccount: XXXXXXXX 10 | symbol: 11 | note: 12 | isInvestment: false 13 | catId: 1405 14 | category: Auto Insurance 15 | merchant: test 16 | date: 05/02/2020 17 | amount: 0.01 18 | mtIsExpense: true 19 | mtCashSplitPref: 2 20 | token: XXXXXXX -------------------------------------------------------------------------------- /sample mint category menu 2020-05-02.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sample request header 2020-05-02.txt: -------------------------------------------------------------------------------- 1 | :authority: mint.intuit.com 2 | :method: POST 3 | :path: /updateTransaction.xevent 4 | :scheme: https 5 | accept: */* 6 | accept-encoding: gzip, deflate, br 7 | accept-language: en-US,en;q=0.9,la;q=0.8 8 | adrum: isAjax:true 9 | content-length: 323 10 | content-type: application/x-www-form-urlencoded; charset=UTF-8 11 | cookie: XXXXXXX 12 | origin: https://mint.intuit.com 13 | referer: https://mint.intuit.com/transaction.event?accountId=XXXXXXX 14 | sec-fetch-dest: empty 15 | sec-fetch-mode: cors 16 | sec-fetch-site: same-origin 17 | user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36 18 | x-requested-with: XMLHttpRequest --------------------------------------------------------------------------------