Card statement reconciliation
When the card statement closes, Claude Code reads the statement export and your expense export, matches every charge to a submitted expense with a script, and drafts one statement per cardholder: what matched, and what still needs a receipt or an explanation. A second model checks the totals tie back to the statement. You tick the statements to send, and each person gets only their own.
- Find exports Shell
- Match charges Claude Code · Sonnet
- Check the totals Claude API · Opus 5
- Pick statements to send You
- Send statements Shell
What each step does
Find exports
ShellPicks the newest statement export and the newest expense export in the folder.
Match charges
Claude Code · SonnetMatches every card charge to a submitted expense with a script and drafts one statement per cardholder.
Check the totals
Claude API · Opus 5A second model checks every statement adds up and ties back to the card statement total.
Pick statements to send
YouYou tick the statements to send. Reject with a comment and they are redrafted with your notes.
Send statements
ShellSends the picked statements to your finance inbox through a webhook.
From copy to first run
- Copy the pipeline JSON with the button above.
- In Jimothy, open Pipelines → New pipeline, paste it under “Or import a pipeline JSON” and click Import.
- Under Workspace, choose the folder where you save each month’s card statement and expense exports.
- Fill in the variables below under Pipeline settings → Variables.
- Triggers → Add trigger → Schedule, cron 0 8 3 * * (08:00 on the 3rd, after the statement closes). Change the day to match your card’s closing date.
Export the card statement as CSV with cardholder, card last four, transaction date, merchant and amount, and put “statement” in the file name. Export submitted expenses for the same period with employee, date, merchant and amount, and put “expense” in its file name. Without an expense export, every charge is listed for the cardholder to account for.
What to fill in
The prompts and commands read these as {{vars.<name>}}. The examples are placeholders: replace them with your own.
| Variable | What to put in it | Example |
|---|---|---|
company | Your company name, used to sign statements. | Acme Inc. |
cardholders | One cardholder per line: name as it appears on the statement, then their email. Charges for anyone not listed are reported to you instead of emailed. | Dana Lee, dana@acme.example Sam Ortiz, sam@acme.example |
dateWindowDays | A charge matches an expense when the amounts are equal and the dates are at most this many days apart. | 3 |
replyByDays | How many days people have to clear their unmatched charges. | 5 |
sendWebhookUrl | Where the picked statements go, e.g. a Zapier hook that emails each one from your finance inbox. | — |
Show the pipeline JSON
{
"jimothyPipeline": 1,
"name": "Card statement reconciliation",
"description": "Matches card charges to submitted expenses and sends each cardholder their monthly statement with the charges still to clear.",
"icon": "workflow",
"color": "#059669",
"repo": {
"mode": "inplace",
"localPath": ""
},
"concurrency": 1,
"variables": {
"company": "Acme Inc.",
"cardholders": "Dana Lee, dana@acme.example\nSam Ortiz, sam@acme.example",
"dateWindowDays": "3",
"replyByDays": "5",
"sendWebhookUrl": ""
},
"steps": [
{
"id": "find",
"name": "Find exports",
"type": "shell",
"description": "Picks the newest statement export and the newest expense export in the folder.",
"timeoutMinutes": 1,
"command": "s=$(ls -t -- *statement*.csv 2>/dev/null | head -1); [ -n \"$s\" ] || { echo \"Put this month's card statement export (a .csv with \\\"statement\\\" in its name) in $(pwd)\"; exit 1; }; e=$(ls -t -- *expense*.csv 2>/dev/null | head -1); echo \"Statement: $s\"; echo \"Expenses: ${e:-(none)}\""
},
{
"id": "match",
"name": "Match charges",
"type": "agent",
"description": "Matches every card charge to a submitted expense with a script and drafts one statement per cardholder.",
"harnessId": "claude-code",
"model": "sonnet",
"dependsOn": [
"find"
],
"timeoutMinutes": 30,
"prompt": "You are reconciling this month's company card statement for {{vars.company}}. The exports in the current folder are:\n{{steps.find.output}}\n\nCardholders (name on the statement, email):\n{{vars.cardholders}}\n\n1. Inspect the files to learn their columns. Write and run a script (Python or awk) that does all of the matching and arithmetic. Never add up amounts in your head; every number you report must come from the script's output.\n2. Match each card charge to at most one submitted expense from the same person where the amounts are equal to the cent and the dates are at most {{vars.dateWindowDays}} days apart. Prefer the closest date, then the most similar merchant name. Refunds and credits are charges too.\n3. For each cardholder, compute: number of charges, total spend, matched total and unmatched total. Also list expenses submitted as paid by card that have no charge on the statement.\n4. Save every charge with its match status (matched, with the expense it matched, or unmatched) to a new file card-reconciliation-<statement period>.csv. Do not modify or delete any other file.\n\nYour final answer is Markdown in exactly this shape:\n## Checks\nStatement total, the sum of the cardholder totals (they must be equal), number of charges, number matched, and any cardholders on the statement who are not in the cardholder list.\n\n### Statement 1: <Cardholder> · <total spend> · <n> unmatched\nTo: <email from the cardholder list, or \"(not in cardholder list)\">\nSubject: Your {{vars.company}} card statement for <period>\n\n<email body>\n\nOne \"### Statement\" section per cardholder, most unmatched charges first, and nothing after the last one. Each email is plain text: a one-line summary of their spend, then a table of the unmatched charges (date, merchant, amount) asking them to submit each as an expense with a receipt within {{vars.replyByDays}} days, or to reply straight away if they don't recognise a charge. Then list their matched charges briefly so they can see everything on their card. If nothing is unmatched, say so and thank them. Sign it \"Finance, {{vars.company}}\".\n{{#if steps.review.output}}\n\nA reviewer checked your previous answer. Fix every problem it lists:\n{{steps.review.output}}{{/if}}{{#if steps.pick.output}}\n\nYour previous draft was rejected with this feedback. Rewrite it:\n{{steps.pick.output}}{{/if}}"
},
{
"id": "review",
"name": "Check the totals",
"type": "agent",
"description": "A second model checks every statement adds up and ties back to the card statement total.",
"harnessId": "anthropic-api",
"model": "claude-opus-5",
"dependsOn": [
"match"
],
"timeoutMinutes": 10,
"passPattern": "VERDICT:\\s*APPROVE",
"loopBackTo": "match",
"maxLoops": 2,
"prompt": "Review this card reconciliation before the statements go out to cardholders.\n\n{{steps.match.output}}\n\nCheck that:\n- the statement total in Checks equals the sum of the cardholder totals;\n- in each statement, the unmatched count in the heading equals the rows in its unmatched table, and the matched and unmatched charges add up to its total spend;\n- each email only contains that cardholder's own charges;\n- every statement has a To: address, or says the person is not in the cardholder list.\n\nStart with a three-line summary for the finance team: total spend, number of charges still unmatched, and anything that needs their attention before sending. Then list each problem you find with the cardholder it affects. End with exactly one line: VERDICT: APPROVE if there are no problems, otherwise VERDICT: CHANGES_REQUESTED."
},
{
"id": "pick",
"name": "Pick statements to send",
"type": "approval",
"description": "You tick the statements to send. Reject with a comment and they are redrafted with your notes.",
"dependsOn": [
"review"
],
"loopBackTo": "match",
"maxLoops": 3,
"choices": {
"fromStep": "match",
"mode": "multiple"
},
"approvalMessage": "Pick the statements to send. Leave out anyone not in the cardholder list, or reject with a comment to have them redrafted.\n\n{{steps.review.output}}"
},
{
"id": "send",
"name": "Send statements",
"type": "shell",
"description": "Sends the picked statements to your finance inbox through a webhook.",
"dependsOn": [
"pick"
],
"timeoutMinutes": 2,
"command": "printf '{\"statements\":%s}' {{steps.pick.selected | json}} | curl -fsS -X POST -H 'Content-Type: application/json' --data @- {{vars.sendWebhookUrl}}"
}
]
}More for accounting & finance, and beyond
Month-end variance commentary
Compares this month’s trial balance with last month’s, explains every material swing, and files the write-up after you sign off.
Accounting & financeExpense policy check
Checks each submitted expense against your written policy. Clean ones go straight through; only the flagged ones wait for you.
Accounting & financeInvoice intake and GL coding
Reads each incoming vendor invoice, extracts the details, suggests GL codes from your chart of accounts, and sends it to your AP tool after you check it.