taxtrace
Prepare a US federal and state return from your own documents, on your own machine, with every number traced to the page it came from.
taxtrace does not file anything. It reads a folder of your documents (W-2s, 1099s, 1098s, property manager statements, receipts, foreign bank statements, last year's return) and produces an entry sheet you type into consumer tax software, an FBAR account list, and a list of questions for you: anything it couldn't settle from the documents alone.
If taxtrace helps with your return, a ⭐ on GitHub is how I know: taxtrace sends nothing home, so stars are the only count of who uses it. If it saved you a preparer's fee, a $1 coffee says so too.
Three rules hold everywhere:
- Nothing is guessed. Every value carries the file, the page and the exact text it was read from, and a deterministic check rejects any value whose quote isn't on that page.
- Your documents never leave your machine. Where a language model reads them (sorting documents, reading messy receipts), it is a local MLX model on your Mac. The chat can use a cloud model, which only ever sees masked text (see Chat).
- Nothing is decided silently. A receipt that could be a repair or an improvement, a missing 1099, a rate choice: each is a question for you, with the evidence next to it.
Status
Early. What works today:
taxtrace redact: masks a document down to its layout, so a layout can be studied (by a person or an AI assistant helping write a parser) without its values, names or places. Every digit becomes#, and every word the IRS's own publications don't use in lowercase becomes*s, so names, streets and towns go without anyone listing them.taxtrace import,outlineandunlock: sort a preparer's export into the workspace, list the forms in each document, and check passwords kept in the Keychain.taxtrace prior: reads a filed return's carryforwards (see Carryforwards).taxtrace k1: reads each Schedule K-1 (Form 1065) face, every box and code, its statements, the Schedule K-3, and North Carolina's D-403 NC K-1 (see K-1s).taxtrace passive: each passive activity's year and the loss it carries in (see Passive activities).taxtrace 1098: reads each Form 1098 mortgage interest statement (see Form 1098).taxtrace 1099: reads the 1099-DIVs and 1099-INTs on brokers' consolidated 1099 statements (see Forms 1099-DIV and 1099-INT).taxtrace rental: puts the rental's Schedule E together, line by line (see Schedule E).taxtrace qbi: Form 8995, the qualified business income deduction (see Form 8995).taxtrace paystub: reads a pay stub's income tax withheld, and projects the year's (see Estimated tax).taxtrace estimates: the year's withholding and estimated payments against the safe harbor, quarter by quarter (see Estimated tax).taxtrace ira: each spouse's IRA deduction and the Roth IRA's limit at the year's projected income (see IRAs).taxtrace itr: reads India's income tax return (ITR-2): a property sale, the interest, the tax India kept (see India's return).taxtrace bank: reads a foreign bank's statement of account: interest, a tax refund, balances (see Bank statements).taxtrace ui: the workspace in the browser, with a chat (see Chat).
Planned, in order: the foreign side (interest at Treasury rates, foreign tax credit, FBAR, Form 8938 check), the entry sheet, and a year-over-year check.
Install
pip install -e ".[dev,mcp]"
taxtrace[chat] adds the chat's Claude SDK, taxtrace[local] the model that runs on your Mac and taxtrace[mcp]
an MCP server.
Use
taxtrace import ~/Downloads/portal-export.zip # filed returns to prior-returns/, the rest by tax year
taxtrace redact ~/TaxData/prior-returns # writes ~/TaxData/redacted/*.txt
A path that isn't one from where you are is looked up in the workspace: taxtrace k1 2025 reads
<workspace>/2025.
taxtrace outline <files> lists each page's masked heading: which forms a document holds, in order (--match '1116|4562' lists only the pages naming them). taxtrace words <pdf> --page 2 --rows shows a page's words, masked,
with where each one sits (--x 300:450 and --top 70:240 narrow it to a region): the geometry of a printed form, for
writing a reader of it. --marks adds the drawn shapes, so a checkbox ticked with a drawn mark shows. --page 19-20,69, --page all and several files take one run, so files that share a Keychain entry cost one approval.
taxtrace show 'prior-returns/2025*' --page 19 opens a document in Chrome at a page, for you to read yourself.
A scanned PDF (pictures of pages, no text) is read through ocrmypdf
on your machine (brew install ocrmypdf), once: the searchable copy is kept under the original's sha256
in <workspace>/ocr-cache, so the text values are quoted from never shifts. A picture (.jpg, .png: a
phone's photo or screenshot of a statement, as one arrives in a chat) goes the same way. A chat app shrinks
what it sends, though, and a blurred table's rows may not read: bank then saves nothing for that account,
and a PDF from the bank (or the picture sent as a document, uncompressed) is the better copy.
import takes a folder or a zip, such as a tax preparer's portal export with one folder per year. It
skips identity documents (driver's licenses, Social Security cards, passports) and anything already
imported, and records every file it places, with its sha256, in imports.jsonl.
Your documents and everything derived from them live in a workspace folder, ~/TaxData or
$TAXTRACE_HOME, which taxtrace refuses to place inside a git repository. Names that are also
ordinary words (Will, Grace) go in <workspace>/redact-terms.txt, one per line.
A password-protected PDF is read with a password kept in the macOS Keychain, never in a file or on a
command line. One default entry covers every return that shares a password; an entry under a file's
name wins for that file, and one under the name before its first - covers a bank's monthly statements
(quillbank for quillbank-4410-2026-01.pdf, quillbank-4410-2026-02.pdf, ...). security asks for it
without echoing, and -T "" trusts no app, so macOS asks you (Allow / Deny) each time anything reads it,
taxtrace included:
security add-generic-password -s taxtrace -a default -T "" -w
security add-generic-password -s taxtrace -a "2024 Return.pdf" -T "" -w # just this file
security add-generic-password -s taxtrace -a quillbank -T "" -w # quillbank-*.pdf
Click Allow, not Always Allow: that would add the reader to the trusted list and end the prompts.
That prompt only takes the typed password. To approve each read with Apple Watch or Touch ID instead:
taxtrace keychain setup # the default entry and the chat's key
taxtrace keychain setup --account "2024 Return.pdf" # and a file's own entry
setup builds a small helper (taxtrace/macos/keychain_gate.swift, compiled with the Xcode command line tools)
and moves each entry over to it. macOS asks for the entry the old way one last time, and the secret is never
retyped or shown. From then on the entry trusts only the helper, and the helper releases it only after macOS's
own approval: Apple Watch, Touch ID, or the password where neither is set up. Every read is still yours to allow.
Only the entries setup moved go through the helper; any other is read the classic way, with its one prompt. If
you deny the classic prompt during setup, it says so and leaves that entry as it was: run setup again.
Once the helper is built, store a new password with taxtrace keychain store <name> (a file's name, its issuer's
such as quillbank, or default): security asks for it unechoed, then the helper moves it over the way setup
does, with one classic prompt. An entry stored with a bare security add-generic-password -T <helper> stays
security's: macOS adds its own prompt to every read the helper makes, on top of Apple Watch or Touch ID.
taxtrace reads each entry at most once per command, so a folder of returns sharing the default password
costs one prompt. taxtrace unlock says which entry opens each locked file, and how each stored password
is shaped (aaaa99999), never what it is. A password pdfminer turns down is tried once more with qpdf
(brew install qpdf), which gets it on stdin and decrypts a copy into a temporary folder for the read: when qpdf
turns it down too, the stored password is wrong, not the reader.
Another tool can read an entry setup moved, such as an API key it shares with the chat, without the entry
trusting that tool too:
taxtrace keychain read anthropic --for jobwatch # the secret on stdout, once approved
The prompt names the item and the tool. The secret goes to the program that runs the command, never to a
terminal, and an entry setup didn't move isn't read this way.
A tool that reads the key unattended (a background service after a restart) has nobody there to approve, and the
approval times out. For a key that guards little, such as one with a spend limit, taxtrace keychain release anthropic moves it back for good: one approval, then security saves it again (the secret on its stdin, never
in a command line) and it reads without any prompt, taxtrace's chat included. The document passwords can't be
released.
When a read asks for the password instead of Apple Watch or Touch ID (a Watch out of range, asleep or locked
falls back to it), taxtrace keychain check says which ways macOS offers right now and why not, and
check --try shows one Watch / Touch ID prompt and prints what came back. Neither rebuilds the helper or touches
an entry.
Besides PDFs and text files (.txt, .csv), Word documents (.docx: one line per paragraph and per table row) and Excel workbooks (.xlsx: a page per sheet, one line per row) are read, with Python's own zip and XML modules. A password-protected Office file isn't read: save an unprotected copy into the workspace.
redact, outline and unlock with no paths read the whole workspace, leaving out what taxtrace wrote
there itself (redacted/, ocr-cache/).
Carryforwards
taxtrace prior # every return in <workspace>/prior-returns
taxtrace prior --layout # also each line a value came from, masked
taxtrace prior --words 23-26,71 # also those pages' masked words, in the same run (one Keychain approval)
Next year's return starts from last year's: each asset's depreciation so far, and each passive activity's loss
that couldn't be taken yet. prior reads them from the preparer's own printouts: the federal depreciation
schedule (Drake's "Federal Depreciation Schedule", or its "Depreciation Detail Listing", whose land rows carry
forward as cost with no depreciation) and Form 8582's Worksheets 5 and 6. Where the return prints "Next Year's
Depreciation Worksheet", each asset's deduction there is checked against a year rolled forward. The values go to <workspace>/carryforwards/<return>.json, each with its page and the exact
line it was read from. The terminal gets counts, failed checks and questions, masked like redact's output.
Nothing is taken on trust:
- Every row is added up against the schedule's totals line, and each asset's depreciation so far against its basis.
- On Form 8582, each activity's ratio is checked against its share of the loss, its unallowed loss against its ratio of the total, and Worksheet 6 against Worksheet 5.
- Worksheets 1 and 3 list every passive activity, with income as well as loss: its net income, net loss, the unallowed loss it brought in, and the overall gain or loss. A row leaves the columns it doesn't use empty, so each amount is read by where it sits, under its column's heading. Each row has to come to its overall gain or loss, and the rows to the total line.
- A schedule printed twice is kept once, and a second copy that disagrees is a question.
- The alternative minimum tax and book copies are skipped. So is next year's projected schedule, which is checked to start from this year's accumulated depreciation.
- A Section 179, bonus or salvage amount, or business use under 100%, is a question for you, not a guess.
- Schedule B (Form 1116) gives the foreign tax carried to next year, per category of income (passive, general...) and again for the alternative minimum tax, by the year each tax arose: its columns are the ten years before this one, this year and a total. Each column's lines have to combine (line 3 is lines 1 and 2, line 8 is lines 3 through 7), the subtotal has to carry from page 1 to page 2, and the total has to add up its columns. The AMT copy's page 2 doesn't say it's the AMT copy, so it goes with the nearest page 1. A box left unchecked is a question. The terminal shows only the years a carryover arose in.
- The Carryover Worksheet (Drake's "List of items that will carryover to the next tax return") is read row by
row: state and local taxes paid, the QBI loss, the foreign tax credit (regular and AMT), last year's tax for
the estimated-tax safe harbor, each spouse's IRA basis. Its foreign tax credit has to equal Schedule B's
carryover, and its heading has to name the next year. Anything under Passive Activity or At Risk Limitations
is a question, since no copy seen so far had rows there.
--layoutlists each row's label and which amounts it has (regular, AMT, taxpayer, spouse), never the amounts. - Form 8995 (the simplified QBI deduction) gives each business's qualified business income (line 1, continued on its own page past five businesses) and lines 2 to 17. Its arithmetic is checked line by line, its QBI and REIT/PTP losses carried on (lines 16 and 17) have to be the Carryover Worksheet's and the next year's lines 3 and 7.
- Form 1040 gives the lines next year's estimated tax goes by: the adjusted gross income, the total tax, the withholding, the estimated payments, the refundable credits, the total payments, the overpayment, what of it went to next year, and the estimated tax penalty. Each is found by its number and words of its label, and the payments have to add up.
- Returns years apart are checked against each other. An asset's depreciation is rolled forward a full year at a time, and has to match the later return's prior depreciation. A miss says how far off it is in years of depreciation (0.46 years is a first, partial year left out), with no amounts. A year's foreign tax carried to the following year has to be the next year's carryover from the prior year, year by year.
taxtrace prior --project 2025 # roll the latest schedule forward, for returns not in hand yet
--project writes <workspace>/carryforwards/projected-<year>.json: each asset's depreciation for every
year since the latest return, marked as projected until that year's return arrives to confirm it.
--layout also shows each asset's prior and current depreciation in years. That's enough to spot a
missing or partial year without seeing an amount.
K-1s
taxtrace k1 2025 # every K-1 face in <workspace>/2025
taxtrace k1 2025 --layout # also each value's text, masked, and where it sits
A K-1's face is a printed form: each amount sits in its box under the box's number, with its code beside it, so
read in order an amount lands next to whatever label is beside it on the page. k1 reads by position instead. It
finds Part III's two columns by their first boxes and gives each amount to the nearest box number above it in its
column. Parts I and II are read by their labels: the EIN, the partnership, the partner (last four digits of the
TIN), each check mark, profit, loss and capital shares, liabilities, and the capital account. The values go to
<workspace>/k1/<document>.json, each with its page, position and exact text. The terminal gets the boxes and
codes that hold amounts, with failed checks and questions, never an amount or a name.
- Item L's capital account has to add up: beginning, plus contributions, this year's income and other changes, less withdrawals, is the ending balance. Each share has to be between 0% and 100%, and each amount in a box with codes has to have one.
- A face printed twice for the same partner counts once. The same amounts for another partner, such as a spouse, are that partner's own K-1.
- An IRA's K-1 (item I2 checked) is marked as one: its income stays in the account, off the return.
- An amount the face sends to the partnership's statement ("STMT", "See statement") is read from the statement pages after the face. A "Line 20, Code Z" or "Box 19C" reference, or a code in a box's table, ties each amount line under it to its box and code, with its description; a label wrapped around its amount (Statement A's 199A grid) is put back together. Totals and item L's reconciliation, which repeat amounts given elsewhere, are left out. A statement amount the pages don't give is a question.
- Each Section 199A amount is marked with its kind (qualified business income, W-2 wages, UBIA, REIT dividends, PTP income) and the trade or business it's for. An aggregated group's totals are kept but marked as the group's, so they're never added in a second time, and each one has to be its businesses' amounts added up.
- A final K-1, or more than one activity (boxes 22, 23), is a question.
North Carolina's partner K-1 (Form D-403 NC K-1) is read the same way, by where things sit. Each of its eleven lines has its amount just above the line's label, and the partnership's FEIN, the partner's number (only its last four digits are kept) and the three Yes/No answers are read by their labels. It goes to the same folder, with where the D-400 takes each line.
- Line 1 has to be the same partnership's federal K-1, Part III lines 1 to 11 added up, as the form says. The federal K-1 can be read in the same run or an earlier one; if none has been read, that's a question.
- Lines 6 to 9 are for nonresident partners and 10 and 11 for taxed partnerships: an amount there on a K-1 whose answers say it isn't one fails.
- Additions and deductions (lines 2, 3) and credits (line 4) are a question: the form doesn't say which Schedule S or D-400TC line each belongs on.
A Schedule K-3 (the partner's international items) is read for Form 1116: box E's marks for which parts apply, and every amount in Part II (gross income and deductions by source and category), each with its line, its country row and country code, and its column, from (a) U.S. source through the foreign categories to the total. Part III's foreign taxes are noted.
- Each row's columns have to add up to its total, and each total line to the lines its label names ("combine lines 1 through 23", "subtract line 54 from line 24").
- Part II's interest, dividends, gains and section 1231 lines have to match the same partnership's K-1 boxes, and a K-1 whose box 16 says a K-3 is attached has to have one read, now or on an earlier run.
- The terminal says whether any of Part II is foreign source and in which categories. Foreign taxes are a question for now: their table isn't read yet.
Passive activities
taxtrace passive # the latest year of the K-1s read
taxtrace passive --year 2025
Form 8582 limits passive losses, and what it doesn't allow carries to the next year activity by activity. passive
starts from the latest return's worksheets as prior saved them (every activity on Worksheets 1 and 3, and what
each carries on from Worksheet 5) and goes on with this year's K-1s as k1 saved them. It opens no document. Each
activity is matched to its partnership's K-1 by the words of its name, and its year is the K-1's boxes 1, 2, 3
and 10, less box 12. Interest, dividends and portfolio deductions aren't the activity's. A K-1 no activity matches
is a new activity. The ledger goes to <workspace>/passive/<year>.json; the terminal gets each activity's
masked name, where its year comes from and whether it's income or a loss, never an amount.
- An activity with no K-1 this year is a question: sold, ended, or missing from the folder? A full disposition (a final K-1) frees the loss it carries.
- What each activity carries in comes from last year's Worksheet 5. When last year's copy has none, its Carryover
Worksheet's Passive Activity section printed empty means nothing carries in; failing that, last year's ledger
(activities sold last year dropped) and the Form 8582
passiveworked out for it. Each is a note to confirm against the filed Worksheet 5, and with none of them what each carries in is left unknown. - A return whose Worksheet 5 carries losses on, followed by a return whose Carryover Worksheet lists none, is a question: the next year's passive income allowed them all, or they were left off and lost to every later year.
- The rental's year comes from Schedule E (
rental), and a box 13 deduction whose code doesn't settle whether it's the activity's is a question.
With every activity's year and carried loss known, passive works the year's Form 8582 out and saves it with the
ledger:
- Each activity goes in Part IV (rental real estate with active participation: the latest return's Worksheet 1) or Part V (every other one, a partnership's new activity too). Line 3 adds them up; zero or more, and every loss is allowed.
- Part II's special allowance (up to $25,000, phased out between $100,000 and $150,000 of modified adjusted gross
income) needs
--confirm magi=AMOUNT; married filing separately also needslived_apart=yes|no. It goes to the Part IV activities first (Part VI). - The rest of line 3's loss is unallowed, shared by the activities with an overall loss in proportion to their losses (Part VII), and carries to next year. An activity disposed of in full at an overall loss is off the form: its losses, carried ones too, are allowed in full.
- Every filed return whose worksheets were read is worked out again from its own Worksheets 1 and 3 and checked against its Worksheet 5: the same arithmetic, rehearsed on the preparer's numbers.
The terminal says what each activity's loss does (allowed, part carried on, off the form), never how much.
Form 1098
taxtrace 1098 2025 # every Form 1098 in <workspace>/2025
taxtrace 1098 2025 --layout # also each value's text, masked, and where it sits
A lender's 1098 is a printed form too. Each box is found by its number and the first words of its label, matched
with the spaces taken out, since a lender's print can split a word ("M ortgage"). Its value is what sits under the
label, from the box's number to the next box's label beside it or below it. The lender's TIN, the borrower's (last
four digits only), the account number and the calendar year are read by their labels. The values go to
<workspace>/forms/<document>.json; the terminal gets which boxes hold amounts, the checks and the questions.
- Box 1 has to be there, and has to be under 15% of box 2's principal: more is a misread, not a mortgage.
- Box 10 (other) isn't interest. What the lender prints beside it (often taxes or insurance paid from escrow) decides where it goes, so it's a question, as are a refund of interest (box 4), insurance premiums (box 5), points (box 6), more than one property (box 9) and a corrected form.
- Box 8's address, or box 7's mark, says which property the interest is for: a rental's goes on Schedule E line 12, a home's on Schedule A. A 1098 with neither is a question.
Forms 1099-DIV and 1099-INT
taxtrace 1099 2025 # every consolidated 1099 statement in <workspace>/2025
taxtrace 1099 2025 --layout # also the account and each box's label, with its value masked
taxtrace 1099 2025 --unlock # also ask the Keychain for locked documents' passwords
Brokers' statements are seldom locked, so a locked document is skipped without asking the Keychain unless
--unlock is given: the folder's other locked files (a W-2, a return) never prompt.
A broker's consolidated statement prints each form as a column of rows under its title ("1099-DIV Dividends and
Distributions - 2025 ..."), the forms side by side: a box's id, its label, and the amount well right of the label.
A box id printed a little under its label's line is on that row. The year is the title's; the payer's TIN and the
account number are read by their labels. A statement printed twice counts once. The values go to
<workspace>/forms/<document>.json; the terminal gets which boxes hold amounts, the checks and the questions.
- The form's own arithmetic is checked: qualified dividends (1b), section 897 ordinary dividends (2e) and section 199A dividends (5) are each part of the ordinary dividends (1a), and 2b, 2c, 2d and 2f part of the capital gain distributions (2a).
- Box 5 is Form 8995 line 6 (
qbi). - The boxes that change where an amount goes are questions: foreign tax paid (with box 8's country), a return of capital (box 3), tax withheld, liquidation distributions, exempt-interest dividends, and on a 1099-INT the early withdrawal penalty, Treasury interest (North Carolina doesn't tax it), tax-exempt interest and bond premium. So is a corrected form.
Schedule E
taxtrace prior # also reads the filed returns' Schedule E: the property, lines 3 to 20, line 19's list
taxtrace 1098 2025
taxtrace rental --year 2025 # reads the answers and policies in <workspace>/2025, writes schedule-e/2025.json
rental puts one property's Part I together from what's been read, each line with where it came from:
- Rents and expenses from the answers typed into the preparer's questions ("Rental income = $1,200/month", "Repairs = $400/year"), under a rental heading. A month's amount is taken twelve times, and that's a question.
- Mortgage interest (line 12) from the 1098 whose box 8 is the property's address, and its box 10, when the lender's description says taxes or insurance paid from escrow, on line 16 or 9.
- Insurance (line 9) from the landlord policy's declarations page for the property: its annual premium. A policy new this year is a question: an earlier one's premium paid this year counts too.
- Depreciation (line 18) from the latest filed depreciation schedule
priorsaved, rolled forward without opening the return again (orprior --project's, when it's newer). Rolled over more than one year, it's a question.
When the year's own return has been filed and read, each line is checked against its Schedule E: a rehearsal that shows which lines the build and the preparer part on, and whether taxtrace's is more or less, never by how much.
What only you know is settled with --confirm, kept in schedule-e/<year>-confirmed.json for every later run:
fair_rental_days=365, personal_use_days=0, rent_all_year=yes, rent=AMOUNT (the rent every tenant paid in the
year, together, in place of the answers' monthly figure), earlier_policy=AMOUNT (a premium paid this year for a
policy before the one read, 0 for none), dues=AMOUNT (association dues, line 19), and cleaning=AMOUNT and
repairs=AMOUNT (the year's receipts added up, lines 7 and 14, a repair a tenant paid out of the rent among them).
A document goes with the property when its address has the same house number and street. A line the latest filed Schedule E had that this year has nothing for (association dues on line 19) is a question, as are the fair rental and personal use days. Locked documents are skipped without a Keychain prompt: answers and policies aren't locked.
Form 8995
taxtrace qbi --year 2025 # writes qbi/2025.json from what k1, rental and prior saved
taxtrace qbi --year 2025 --layout # also each K-1's Section 199A statement rows: what each is and its sign
taxtrace qbi --year 2025 --confirm k1_4567=business --confirm rental=not
qbi puts the simplified Form 8995 together from what's been saved, opening no document:
- Each partnership's qualified business income is its K-1's passive income or loss (boxes 1, 2, 3 and 10, less 12), the figure a preparer's Form 8995 carries, rather than the Section 199A statement's own total.
- The rental's is Schedule E's net from
rental. - A passive loss Form 8582 doesn't allow isn't qualified business income until a later year allows it. Each
activity's amount is what the year's Form 8582 let through (its overall gain or loss, less what it carried on):
the filed one when the year's return is read, otherwise the one
passiveworked out. With neither, the amounts are before that limit, and that's a question. - Which businesses count follows last year's filed Form 8995: a partnership it listed (by its EIN's last four
digits) that has income or a loss this year, and the rental if it listed a Schedule E. One it didn't list is a
question for
--confirm k1_<last four>=business|not(orrental=), kept inqbi/<year>-confirmed.json. - Line 6 is the K-1s' REIT dividends and PTP income and the year's 1099-DIVs' section 199A dividends (box 5, from
taxtrace 1099). Last year's losses carried on (lines 16 and 17) come in on lines 3 and 7. Lines 11 to 15 need taxable income, so the deduction is known only when line 10 is zero. The threshold for the simplified form follows the filing statusforeign --confirm filing=kept; above it Form 8995-A applies, which taxtrace doesn't do yet.
When the year's own return has been filed and read, every line and business is checked against its Form 8995. A business whose amount disagrees names the other figure the filed one matches (the Section 199A statement's total, an aggregated group's, the filed Schedule E's net), so a rehearsal shows where a build and the preparer part ways. The terminal gets which businesses are income or a loss, the checks and the questions, never an amount.
Estimated tax
taxtrace prior # reads last year's Form 1040 and Carryover Worksheet
taxtrace paystub 2026/payslip.pdf # the latest pay stub: the year's withholding, projected
taxtrace estimates --year 2026 --confirm paid_2026-09-15=2500
A pay stub's header row gives the pay period and the payment date, and its taxes table each tax's amount this paycheck and the year to date ("FIT Withheld", "SIT Withheld (NC)"). The year's withholding is the year to date's plus this paycheck's for each one left: the pay period's length says how often you're paid, and each later paycheck is paid as long after its period ends as this one was, so a December period paid in January counts next year. A bonus or a new W-4 changes that, so it's a question.
taxtrace paystub --rate 47 says what each paycheck left would take home at a 47% pre-tax 401(k) deferral: this
paycheck's net pay, less the deferral's change, plus the federal withholding it moves (the percentage method: the
year's brackets past the standard deduction, on the paycheck's taxable wages times the paychecks in a year) and the
state's at its flat rate, until the year's limit stops the deferrals. The terminal gets each paycheck's take-home as
a share of this one's and the cents of take-home a deferred dollar costs; the amounts go to the file, and --show
prints them in your own terminal only.
No underpayment penalty is due when the year's payments reach the smaller of 90% of the year's own tax and 100% of last year's (110% when last year's adjusted gross income was over $150,000, $75,000 married filing separately). Last year's tax is the Carryover Worksheet's federal tax liability, or Form 1040's line 24 less line 32.
A quarter of it is due by each of April 15, June 15, September 15 and January 15, and each quarter is checked on
its own. An estimated payment counts on the day it's made, so a January payment can't make up an earlier quarter.
Withholding counts as paid evenly through the year, however late, so more withholding before December 31 makes up
every quarter, and estimates says how much would. Last year's overpayment applied to this year counts in April.
What only you know is kept in estimates/<year>-confirmed.json: withheld=AMOUNT (the year's federal tax
withheld in all, in place of the pay stub's projection, when there's more than one job or a 1099 withholds too), paid_YYYY-MM-DD=AMOUNT (each estimated payment)
and tax=AMOUNT (the year's own tax, once it's worked out). The terminal says which quarters are met or short;
the amounts are in estimates/<year>.json.
IRAs
taxtrace ira --year 2026 # from the latest pay stub and last year's Form 1040
taxtrace ira --year 2026 --confirm other=9000 # the year's income besides the wages, once it's known
A married couple filing jointly can each put the year's limit into an IRA, a spouse without pay on the other's
pay. How much of it is deductible phases out with income: over a lower range for a spouse covered by a 401(k), a
higher one for a spouse who isn't but is married to one who is. A Roth IRA has its own range. ira projects the
year's income from the pay stub's federal taxable wages to December 31, as they are and with the 401(k)'s room
filled (which lowers it), plus last year's interest, dividends and Schedule 1 income, and gives each spouse's
deduction and the Roth IRA's limit in both cases. The income itself is masked.
To do
taxtrace todo # open items by due date, undated last
taxtrace todo --due # overdue, plus what's due in the next 7 days
taxtrace todo --due 14 # ... the next 14 days
The list is todo.md at the root of the workspace, written by hand (taxtrace never reads it as a tax document).
The page shows it above the documents, in groups (overdue, today, this week, later, no date), and a card's checkbox
marks it done.
## Estimated tax
Any line that isn't an item, like this one, is ignored.
- [ ] **Thu 1/14/2027** Pay the fourth-quarter estimate, from `estimates`
- the amount is in estimates/2026.json
- [ ] check the account balance
- [ ] **Wed 10/28, time TBD** Call the plan administrator about the **rollover**
- [x] Order the tax software
An item is - [ ] text (open) or - [x] text (done) under a ## Section. A leading bold date is its due date, with
a year (**Fri 1/15/2027**) or without (**Wed 10/28**, the date from 2 months back to 10 months ahead); undated
items are fine. Indented - text lines are notes and indented - [ ] text lines sub-tasks. Marking an item done
changes that line's [ ] to [x] and nothing else, and refuses if the line was edited since the page loaded.
India's return
taxtrace itr 2025 # writes forms/itr-<assessment year>.json
A US person who sells property in India files India's ITR-2 as a non-resident: the buyer withholds TDS at
registration and the return settles it, often with a refund. itr reads the printed return row by row (amounts in
Indian grouping, 12,34,567):
- Schedule CG, each property: the purchase and sale dates, the consideration received, the stamp value, the cost, the improvements (with the financial year of each), transfer expenses, exemptions and the gain.
- Schedule OS: the interest, and how much of it is savings-account interest.
- Part B-TTI: the tax, the aggregate liability, the taxes paid (advance tax, TDS, TCS, self-assessment) and the refund. What India kept, the taxes paid less the refund, is what Form 1116 can credit.
India's year runs April to March (assessment year 2025-26 is April 2024 to March 2025), so a sale's own date says which US year it belongs to. The checks: the taxes paid add up, the refund is the taxes paid less the aggregate liability (to the nearest ten rupees, as India rounds it), the aggregate is the net liability plus interest and fees, the TDS schedules match Part B-TTI, each sale's balance and gain add up, and the sale falls in the return's year. Questions: an improvement's date (only its financial year is printed, and each converts to dollars at its own day's rate), a stamp value above the price received (the US return uses the price), and whether the person owned the whole property. Nothing that names the person is kept: no PAN, acknowledgement number or address.
Bank statements
taxtrace bank 2025 # writes forms/bank-<last four digits>-<period start>.json
taxtrace bank --rows 2025 # also each row's date, kind and description, masked
An Indian branch's statement of account is a table: the date, the amount under Withdrawal or Deposit, the balance
("Cr." or "Dr."), then a description that can run onto several lines. bank places each word by where it sits: an
amount belongs to the column whose heading it starts under, and a description line goes with the dated row nearest
it (a description of several lines is centred on its row). Each row gets a kind: interest (its description names the
period it pays for), a tax refund, TDS withheld on interest, or a TDS refund. These give Schedule B and Form 1116 the
interest, Form 8938 and the FBAR the balances, and a cross-check that India's refund arrived.
A bank's monthly "Account Statement" is read too: a row number, dates as 05 Jan 2026, the description left of the
amounts, Indian commas (1,23,456.78), an Opening Balance row and an Account Summary. Its quarterly interest is
credited net of TDS (Int:1234.56 and TAX:123.45), and is read as two rows, the interest and then the TDS withheld.
When only a blurred picture of a statement has arrived (a chat app shrinks what it sends past what OCR reads), type it into a .csv next to it, each row as printed. Its balances are checked one by one like a printed statement's, and a mistyped one isn't saved:
account,000123459876
currency,INR
period,01-04-2026,27-10-2026
opening,"1,02,410.00"
date,withdrawal,deposit,balance,description
05-05-2026,,25000.00,127410.00,Cash Deposit
30-06-2026,,1234.56,128644.56,Int.Pd From:01-04-2026 to 30-06-2026
30-06-2026,123.45,,128521.11,TDS on interest
So is a bank's monthly e-statement of every account the customer holds there ("As on 31-01-26"): a balance summary,
the term deposits, and a page per savings account with the month's transactions (dates as 31-01-26, amounts
right-aligned under Credit, Debit and Balance) between its opening and closing balance lines. Each account is saved as
a statement of its own, with how it's held (single, "former or survivor"). A term deposit's balance is its principal
and the interest accrued on it, and the foreign side takes its interest for the year as what that value grew by:
interest added to a cumulative deposit is income in the year it's added, though none is paid out, and a renewal
carries it into the new principal. A value that falls (TDS taken from the interest at India's year end) is asked
about. The checks: each account's balances follow from its opening balance, its closing balance is its available
balance, and the accounts add up to the balance summary.
The bank's interest certificate on its deposits ("for the financial year ended 31st March 2026") goes through bank
too: per account, the balance on March 31, the interest paid and accrued, and the total tax deducted, checked against
its TOTAL row. It gives what a term deposit's statements don't: the TDS taken from its interest on March 31. The
foreign side takes it as tax withheld that day (Form 1116) and as interest the value never showed, but only when it
fits the fall in the deposit's value that day (at most the fall plus a quarter's interest). Tax taken earlier in
India's year belongs to an earlier US year, so a certificate that doesn't fit is asked about, never counted whole.
India's quarterly TDS certificate (Form 16A until March 2026, Form 131 from April 2026) goes through bank as well:
the quarter, the bank, each payment of interest, the tax deducted and the challans that paid it to the government,
checked against their totals. India's quarters fall inside calendar quarters, so the foreign side compares each 2026
quarter's tax with the TDS it found for that bank (the statements' TDS rows and the interest certificate's): a match is
noted, and tax left over is asked about, never counted on its own: it was taken where no statement shows it. Form
26AS gives each deduction's day and account. Tax on interest credited in an earlier year (a certificate can list such
credits) still counts in the year it was taken when Form 1116 claims taxes paid, its usual box. foreign --confirm tds_<the quarter's last day, YYYYMMDD>=taken then counts it once, as tax withheld on the quarter's last day and as
interest the deposit's value never showed, and =not leaves it out. Less tax than was found is asked about too. The
bank is matched by the name the certificate prints, from foreign --confirm bank_<the statements' file name's first word>=<name>; <address>.
taxtrace fbar --year 2026 turns the foreign side into each spouse's FBAR (FinCEN Form 114), item by item:
<workspace>/foreign/fbar-2026.txt is a checklist to type into the BSA e-filing form. Each account is in Part II
(held alone) or Part III (held with anyone else: its statement's mode of operation says, or foreign --confirm joint_<last four>=yes|no). Its maximum value is the highest balance at the Treasury's December 31 rate, rounded up to
the dollar, and marked an estimate until the year is covered. The bank comes from foreign --confirm bank_<the statements' file name's first word>=<name>; <address>. Account numbers stay out of it: they're typed in
from a statement.
The checks: each balance is the one before plus the deposit or less the withdrawal, and the rows add up to the statement's grand totals (or, on a monthly statement, its opening and closing balances are the summary's, and its net interest is the interest less the TDS). A statement runs on India's year (April to March), so a calendar year usually needs two. Of the account number only its last four digits are kept, and nothing that names the holders.
Foreign side
taxtrace foreign --year 2025 # writes foreign/2025.json
taxtrace foreign --year 2025 --confirm owner=spouse
foreign puts a property sold abroad on Form 8949 in dollars, each amount at its own day's rate: the price at the
sale's, the cost at the purchase's, an improvement at the day it was paid. A currency that fell against the dollar
leaves a dollar gain far smaller than the gain taxed abroad (or a loss, which on a personal home isn't deductible).
The tax the other country kept, the taxes paid less the refund, goes to Form 1116 as passive category, at the rate
of the day it was withheld (a refund is translated at the rate the tax was).
Form 1116 claims taxes either paid or accrued, the box under its Part II, and last year's return shows which:
foreign --confirm method=paid|accrued. Accrued, once chosen, binds every later year. Paid counts each tax in the
year it was withheld, at that day's rate. Accrued counts India's tax for its year (April to March) in the US year that
year ends in, as its return for that year finally kept it, at the IRS's average rate for the US year (section 986(a)):
2026 claims the return for April 2025 to March 2026 (assessment year 2026-27). The TDS withheld during the US year is
then kept apart (tds in the output), since that return credits it, and the TDS from April on belongs to next year's
Form 1116.
Foreign tax the limit kept from being credited carries forward ten years. foreign takes it from last year's return
as prior read it: its Schedule B (Form 1116) line 8, per category and for the alternative minimum tax, by the year
each tax arose, is this year's Form 1116 line 10 (carryovers in the output). Without last year's return read, that's
a question (--confirm carryover=none when nothing carries). A tax in its tenth year is flagged: what this year can't
credit of it expires.
The rates are public and fetched once into <workspace>/fx, each kept with its source:
- a day's rate: the Federal Reserve's noon buying rate (FRED,
DEXINUSfor the rupee), or the last business day's before a weekend or holiday; - a year's average, for interest: the IRS's yearly average currency exchange rates;
- December 31, for Form 8938 and the FBAR: the Treasury's Reporting Rates of Exchange.
What only you know is settled with --confirm, kept in foreign/<year>-confirmed.json: owner=self|spouse|joint|other
(on a joint return either spouse's sale counts in full), improvement_dates=YYYY-MM-DD,... (until then an
improvement is at its financial year's mean rate; average keeps that rate when the dates aren't known),
use=investment|personal|rental (asked when the dollar result is a loss), filing=joint|single|separate|head
(for Form 8938's thresholds) and account_<last four digits>=self|spouse|joint (whose an account is). Each spouse
files their own FBAR, of the accounts in their name, so the FBAR is tested per person; an account held with someone
outside the return (a parent) is yours in full. Form 8938 on a joint return takes both spouses' accounts together.
With the bank statements bank saved, foreign also gives each account's year:
- the interest credited in the calendar year, at the IRS's yearly average (Schedule B; passive income on Form 1116), and the TDS withheld on it at each day's rate (a tax Form 1116 credits);
- the highest balance and the balance on December 31, at the Treasury's December 31 rate, and from all accounts together whether the FBAR (over $10,000 at any time) and Form 8938 (by filing status, for someone living in the US) are due. A home held directly isn't a Form 8938 asset; the account its price was paid into is;
- India's refund found among the deposits: what arrived beyond it is India's interest on the refund, Schedule B income in the year it arrived.
India's statements run April to March, so a calendar year needs two. Until both are read, the days not covered are asked for, and the FBAR and Form 8938 stay unsettled unless what's covered already crosses a threshold. While the year is still going, its average and December 31 rates don't exist yet: the latest day's rate stands in for both, and the FBAR and Form 8938 answers say they're estimates.
Chat
pip install -e ".[chat,local]" # Claude's SDK, and the model that runs on your Mac
taxtrace ui # opens http://taxtrace.localhost:8766/
The page lists your workspace by folder and has a chat for tax rules, forms and what a document is for.
It answers with the first model in --models (default claude,agy,local) that can, and moves on to the
next when one can't (no key, quota used up, not installed):
claude: Anthropic's API, withANTHROPIC_API_KEYor a Keychain item holding the key (--keychain, defaultanthropic).agy: Google's Antigravity CLI, on its subscription, run in an empty folder.local: an MLX model on your Mac.
Claude and agy only ever see masked text. Every message, and the list of your documents, goes through the
same masking as taxtrace redact first: digits become #, and words outside the IRS's own vocabulary
become *s. A question with an amount in it, or a name from redact-terms.txt, isn't sent to them at all:
the model on your Mac answers it, and the page says why. Under each question a cloud model answered, the
page shows exactly what it was sent. The chat reads only the document list and what you type: it can't open
a document, change a file or file anything.
Always on (macOS)
An installed app (the page added to your Dock from the browser) only works while the server is running. To start it at login and restart it if it stops:
taxtrace service install # same --port, --models and --keychain as `ui`
taxtrace service status # running or not, the address, the log
taxtrace service uninstall
Run install from the shell where TAXTRACE_HOME is set: a login item doesn't read your shell profile,
so install writes that workspace path, and a PATH that finds agy and Homebrew's ocrmypdf, into
~/Library/LaunchAgents/io.github.taxtrace.ui.plist. No key or password goes in it; Claude's key is still
read from the Keychain. The log is ~/Library/Logs/taxtrace-ui.log.
To install the page as an app, open http://taxtrace.localhost:8766/ in Chrome and click the install icon at
the right of the address bar (or ⋮ → Cast, save and share → Install page as app). It gets its own window and
Dock icon. python scripts/make_icons.py rebuilds the app's PNG icons after an SVG changes.
MCP server and agent skill
taxtrace-mcp lets Claude Code, Claude Desktop or any MCP client look at your workspace: what's in it, what
each document is, which forms read cleanly, and what's on your to-do list. It returns masked text only.
pipx install 'taxtrace[mcp]' # or: pip install 'taxtrace[mcp]'
claude mcp add taxtrace -s user -e TAXTRACE_HOME=~/TaxData -- taxtrace-mcp
Without installing first, uv can fetch and run it: claude mcp add taxtrace -s user -e TAXTRACE_HOME=~/TaxData -- uvx --with 'mcp>=2.2' taxtrace mcp. taxtrace mcp runs the same server as
taxtrace-mcp. For Claude Desktop, add this to claude_desktop_config.json (use the full path of taxtrace-mcp
if which taxtrace-mcp isn't on the app's PATH, such as ~/.local/bin/taxtrace-mcp for pipx):
{
"mcpServers": {
"taxtrace": {
"command": "taxtrace-mcp",
"env": { "TAXTRACE_HOME": "/Users/you/TaxData" }
}
}
}
| Tool | What it gives |
|---|---|
get_status |
Counts: tax years, folders, documents, how many were read, what was saved from them, whether a to-do list exists |
list_documents |
Each document's id, folder and name (masked), kind, and whether it was read; filter to read or unread |
get_document_outline |
A document's pages by masked heading: which forms it holds, in order (a locked PDF is reported, not opened) |
get_form_checks |
Per Form 1098, 1099-DIV or 1099-INT, or K-1: checks passed of total, the ones that failed, the questions left |
get_todo |
todo.md by due date: section, date, days left, group, and masked text, notes and sub-tasks |
Every tool is read-only. The masking is the one the chat uses for a cloud model and taxtrace redact writes:
digits become #, words outside the IRS's own vocabulary become *s, and names in redact-terms.txt go first.
No tool returns an amount, name, ID, account number or the workspace's path; dates, counts and pass/fail results
stay. A locked document is never opened and the Keychain is never asked, so a client can't cause a prompt or reach
a password. taxtrace reads your documents on your machine, as it always does; the client only gets what is left
after masking. A test builds a workspace full of made-up amounts and names and checks that none of them appear in
any tool's output.
skills/taxtrace/SKILL.md is an agent skill that tells an assistant when and how to use
these tools, and that it only ever sees masked text. Copy the folder into ~/.claude/skills/, or zip it to upload
to Claude Desktop (Settings, Capabilities, Skills): cd skills && python -m zipfile -c taxtrace-skill.zip taxtrace.
It's listed in the MCP Registry as io.github.vinayvobbili/taxtrace
once released.
Development
git config core.hooksPath .githooks # once: every push first runs scripts/check
scripts/check # ruff + pytest on Python 3.10 and 3.13, as CI does
python scripts/build_vocab.py # rebuild the redaction vocabulary from IRS publications
Set PRE_PUSH_AUDIT to a command that scans a repository for personal data and exits non-zero on a
finding, and scripts/check runs it too, with the repository's path appended.
Every fixture is invented: no real person, place or amount appears in this repository.
Metadata
Release files for taxtrace 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| taxtrace-0.1.0.tar.gz | 627.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| taxtrace-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.0 MB
Release files / taxtrace-0.1.0.tar.gz
| Download URL | taxtrace-0.1.0.tar.gz |
|---|---|
| Size | 627.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
afec0ab5def138ff055b0c9c15d816794a398ecd919e046373b5c014a01a5432
|
|
BLAKE2b-256 checksum How to use checksums |
72d223d63b60179204686ade622254eccf86afa38b3a9e1e7240a5cf0a194176
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logRelease files / taxtrace-0.1.0-py3-none-any.whl
| Download URL | taxtrace-0.1.0-py3-none-any.whl |
|---|---|
| Size | 395.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
94ef443cf31ad44eceec702c10ff00e5f8d50d1f6ecb68f630f91e1adbb13371
|
|
BLAKE2b-256 checksum How to use checksums |
2dd82a636a37d003de8bc929579d11dd059576a5534ee87440255f41cc449c4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logSecurity for taxtrace 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for taxtrace-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.