You have 40 supplier invoices in a folder and an hour. Typing each into a spreadsheet is dull and you'll make mistakes. Having an AI read them is faster, but the danger isn't the extraction. It's that nobody checks what came out before it reaches the books or the bank. This post shows AI invoice data extraction into Google Sheets with a verification layer in between, and one hard rule: the workflow never pays anything.
I ran the verification script below on 2026-10-09. The sample invoices are invented, as are the vendors, so the output shows how the checks behave, not how accurate any model is. I did not run an extraction model for this post, so the extraction prompt is an untested template.
If you want the code-heavy route with PDF parsing and vision fallbacks, see the invoice extraction agent post. This one is the lighter Sheets workflow, with the verification step as its centre.
What does the workflow look like?
Five stages. Only the first is AI.
- Extract. A model turns the invoice (text or image) into fixed fields.
- Land. Rows go into a Sheet tab called
extracted, exactly as returned, untouched. - Verify. Plain rules compare each row to your own records and mark it
READY_FOR_HUMANorNEEDS_REVIEW. - Review. A person opens the original invoice for every flagged row, and spot-checks a sample of unflagged ones.
- Pay, separately. In your bank or accounting tool, by a person. Nothing in this workflow connects to it.
Note the name of the good verdict. It's not "approved." It means only that a human may now look at it.
Step 1: the extraction prompt
Untested template. Run it on five of your own invoices and compare field by field before you trust it. If your tool supports structured outputs, use them; see Claude structured outputs with JSON and Pydantic.
Extract the fields below from the attached invoice. Return JSON only.
Rules:
- If a field is not visible on the invoice, use null. Never guess.
- Copy numbers exactly as printed, without rounding.
- Amounts as strings with two decimals, no currency symbols.
- Dates as YYYY-MM-DD.
- lines: one entry per line item amount (before tax).
Fields: vendor, number, date, lines (list of strings), tax, total, iban_last4 (last 4 characters of any bank account or IBAN shown), currency.
Two design choices matter. "Use null, never guess" produces gaps your checker can see, instead of plausible inventions. And bank details are reduced to the last four characters, so the Sheet doesn't hold full account numbers. Check with whoever handles your data rules before you put financial documents into any AI tool; what not to paste into ChatGPT is a starting point.
Step 2: the vendor master and PO list
Verification needs something to compare against. Two small tabs in your Sheet:
vendors: vendor name, bank last 4, tax ID.purchase_orders: vendor, maximum expected total.
Fill them from records you trust, not from the invoices being checked. Otherwise a fraudulent invoice would verify itself.
Step 3: the verification script
The rules, as a Python script. It uses Decimal rather than floats so cents don't drift. All data inside is invented. The extraction output is represented as a list of dicts, which is what you'd get after parsing the model's JSON.
from datetime import date
from decimal import Decimal
TODAY = date(2026, 10, 9)
VENDORS = {
"Brightline Print Co": {"iban_last4": "4412"},
"Northwind Hosting": {"iban_last4": "9031"},
"Kestrel Office Supplies": {"iban_last4": "7720"},
}
POS = {"Brightline Print Co": Decimal("2400.00"), "Northwind Hosting": Decimal("600.00")}
def check(inv, seen):
flags = []
d = Decimal
if inv["tax"] is None:
flags.append("missing field: tax")
else:
calc = sum(d(x) for x in inv["lines"]) + d(inv["tax"])
if calc != d(inv["total"]):
flags.append(f"arithmetic: lines+tax={calc} but total={inv['total']}")
key = (inv["vendor"], inv["number"])
if key in seen:
flags.append(f"duplicate of {seen[key]}")
else:
seen[key] = inv["id"]
if date.fromisoformat(inv["date"]) > TODAY:
flags.append(f"invoice date {inv['date']} is in the future")
vm = VENDORS.get(inv["vendor"])
if vm is None:
flags.append("vendor not in vendor master")
elif vm["iban_last4"] != inv["iban_last4"]:
flags.append(f"bank details changed: master ...{vm['iban_last4']} vs invoice ...{inv['iban_last4']}")
po = POS.get(inv["vendor"])
if po is not None and inv["total"] and d(inv["total"]) > po:
flags.append(f"total {inv['total']} exceeds PO limit {po}")
return flags
I fed it seven invented invoices that include one clean pair, a duplicate, a total that doesn't add up, a changed bank account, a future date from an unknown vendor, and a missing tax field. The full script with the sample data is about 75 lines; the part above is the logic you'd keep. Output from my run:
A1 Brightline Print Co BP-1001 READY_FOR_HUMAN
A2 Northwind Hosting NW-5521 NEEDS_REVIEW
- arithmetic: lines+tax=590.00 but total=690.00
- total 690.00 exceeds PO limit 600.00
A3 Kestrel Office Supplies K-88 READY_FOR_HUMAN
A4 Brightline Print Co BP-1001 NEEDS_REVIEW
- duplicate of A1
A5 Kestrel Office Supplies K-91 NEEDS_REVIEW
- bank details changed: master ...7720 vs invoice ...3305
A6 Quickfix Consulting QF-7 NEEDS_REVIEW
- invoice date 2027-01-15 is in the future
- vendor not in vendor master
A7 Northwind Hosting NW-5530 NEEDS_REVIEW
- missing field: tax
Payments made by this script: 0
Five of seven were stopped, and every stop has a reason a person can verify in under a minute. That is the point of the step: a flag turns "read 40 invoices carefully" into "read the 5 that look odd, and sample the rest."
What it does not catch: a fake invoice with correct arithmetic, a real vendor and the right bank details, a wrong amount that still matches the PO, or goods never delivered. Hence the human step and the spot-check of unflagged rows.
Doing it with Sheet formulas instead
If you don't want Python, the same rules fit in a Sheet. In an extracted tab with columns for vendor, number, lines total, tax, total and bank last 4:
- Arithmetic:
=ROUND(D2+E2,2)=ROUND(F2,2)should be TRUE. - Duplicate:
=COUNTIFS(A:A,A2,B:B,B2)>1flags repeats of vendor plus number. - Bank change:
=VLOOKUP(A2,vendors!A:B,2,FALSE)<>G2flags mismatches (wrap inIFERRORto flag unknown vendors). - Combine into a
statuscolumn and colourNEEDS_REVIEWred with conditional formatting.
These are standard Sheet functions, but I haven't run these exact cells against the sample data above, so test them on a few rows first. For Apps Script or API-driven versions, see Claude with Google Sheets for business automation.
Rules that keep it safe
- No payment connection. Don't wire this Sheet to a bank API, payment app or auto-pay rule. Pay from your own bank or accounting tool after review.
- Changed bank details always get a call. Phone the vendor on a number you already had, not one printed on the invoice.
- Keep the originals. Link each row to its source file so reviewers see what the model saw.
- Measure before you trust. Extract 20 invoices by hand and by model, count mismatched fields, and decide your own threshold. I can't give you an accuracy figure because I haven't measured one.
- Log changes. If someone edits an extracted row, record who and why.
- Tax and compliance questions (GST, TDS, e-invoicing) belong with your accountant, not with the model.
If the extraction step fails or goes down, you need a fallback; the pattern is in automation error handling for non-developers.



