A Chime statement PDF can hold more than one account, so a parser has to split it by section before it adds anything up. Each section gets its own beginning balance, rows and ending balance, and each one is proven on its own. ZeraBooks does this when it converts a Chime statement: it reads every section as a separate account. Here is the same idea in about 40 lines of Python. (I founded ZeraBooks. The code below is plain Python and doesn't need it.)

What is actually inside one Chime PDF?

Chime's help center says one statement has a section per Chime account, each with its own title: Checking Account Statement, Savings Account Statement, Credit Builder Secured Account Billing Statement and Chime Card Secured Account Billing Statement. Checking and Savings run on calendar months. Credit Builder follows its own billing cycle.

Chime is a financial technology company, not a bank. The accounts are held at The Bancorp Bank, N.A. or Stride Bank, N.A., and the PDF says which. You get the PDF from Profile, then Documents, in the app or on chime.com.

One honest caveat: I haven't confirmed Chime's exact column names or how it prints negatives. So the text below is a simplified extract, the kind you get after pulling text out of the PDF and cleaning it up. The section titles are Chime's. The rows are fictional.

How do you split it?

Treat the section title as the boundary. Every line after a title belongs to that account until the next title shows up.

import re from decimal import Decimal TEXT = """\ Checking Account Statement Beginning balance 1,076.40 10/03 Rover payout 612.00 10/07 PetSmart -148.37 10/10 Transfer to Savings -250.00 10/17 Rover payout 705.50 10/21 Chime Checkbook, rent -900.00 10/28 State Farm -96.12 Ending balance 999.41 Savings Account Statement Beginning balance 640.00 10/10 Transfer from Checking 250.00 10/31 Interest 1.18 Ending balance 891.18 """ TITLE = re.compile(r"^(.+ (?:Account Statement|Account Billing Statement))$") ROW = re.compile(r"^(\d{2}/\d{2}) (.+) (-?[\d,]+\.\d{2})$") BAL = re.compile(r"^(Beginning|Ending) balance (-?[\d,]+\.\d{2})$") def money(s): return Decimal(s.replace(",", "")) def split_sections(text): sections, cur = {}, None for line in text.splitlines(): if m := TITLE.match(line): cur = sections.setdefault(m[1], {"rows": []}) elif m := BAL.match(line): cur[m[1].lower()] = money(m[2]) elif m := ROW.match(line): cur["rows"].append((m[1], m[2], money(m[3]))) return sections for name, s in split_sections(TEXT).items(): total = sum(r[2] for r in s["rows"]) ok = s["beginning"] + total == s["ending"] print(f"{name}: {s['beginning']} + {total} = {s['beginning'] + total}" f" vs printed {s['ending']} -> {'OK' if ok else 'MISMATCH'}") flat = [m for l in TEXT.splitlines() if (m := ROW.match(l))] naive = Decimal("1076.40") + sum(money(m[3]) for m in flat) print("naive:", naive, "off by", naive - Decimal("999.41")) 

Output:

Checking Account Statement: 1076.40 + -76.99 = 999.41 vs printed 999.41 -> OK Savings Account Statement: 640.00 + 251.18 = 891.18 vs printed 891.18 -> OK naive: 1250.59 off by 251.18 

Do the numbers really add up?

They should, so here is the arithmetic by hand. The account holder is Tomas Vidal, a fictional mobile dog groomer.

Checking: money in is 612.00 + 705.50 = 1,317.50. Money out is 148.37 + 250.00 + 900.00 + 96.12 = 1,394.49. So 1,076.40 + 1,317.50 - 1,394.49 = 999.41, the printed ending balance.

Savings: 640.00 + 250.00 + 1.18 = 891.18.

The naive version is off by 251.18, which is exactly the Savings rows (250.00 + 1.18). That number is the tell. When a combined total is off by the sum of one section's rows, you didn't split.

Why use Decimal and not float?

Because 0.1 + 0.2 isn't 0.3 in a float, and a proof that compares to the cent will fail for reasons that have nothing to do with the statement. Parse money as Decimal from the start and never convert back.

What about Credit Builder?

The title regex already catches "Account Billing Statement", so a Credit Builder section becomes its own key. Don't prove it like a bank account, though. It is a billing statement on its own cycle, so it checks out against its own previous and new balance, and its dates won't line up with October Checking. Keep it as a separate account in your output and you won't mix periods.

What should the output look like?

One table per account, or one table with an Account column. Never one flat list with no account. In ZeraBooks Convert that is the Split choice in the Download dialog: One per account gives each Chime account its own tab in Excel or its own CSV.

ZeraBooks Convert Download dialog, with a Split option that includes One per account

The 250.00 transfer shows up twice in the split output, out of Checking and into Savings. That is correct. It only becomes a bug when both lines land in one list and get read as income.

If you'd rather not maintain the parser, the full walkthrough with a converted month is on Chime bank statement to Excel. ZeraBooks reads the PDF, splits it by account and proves each section before you download. It doesn't connect to Chime; you upload the PDF. Plans are 1,000 pages a month for $79 a month billed annually or $149 monthly, after a 7-day trial.

Split first, then sum. In that order, every time.