Module 07 · business · intermediate
Solutions 07 · Invoice and Receipt Extractor
Exercise 1
In Clean Receipt, replace needs_review with:
{{
(() => {
const o = $json.output;
const s = Number(o.subtotal ?? 0), t = Number(o.tax ?? 0), total = Number(o.total);
const nan = [s, t, total].some(Number.isNaN);
const mismatch = o.subtotal != null && Math.abs(s + t - total) > 0.02;
return !o.total || !o.date || !o.vendor || nan || mismatch;
})()
}}
To test without calling OpenAI: run once, then pin the Extract Receipt Data output (pin icon in the output panel) and edit total to a wrong value.
Exercise 2
Has PDF? (false) → IF "Is Image?" ─true→ OpenAI (Image → Analyze Image) → Parse JSON → Clean Receipt
└false→ Respond 400
- Is Image?:
{{ Object.values($binary ?? {}).some(f => f.mimeType?.startsWith('image/')) }} - OpenAI node, resource Image, operation Analyze Image, input type Binary File, model
gpt-5-mini, prompt: "Extract vendor, invoice_number, date (YYYY-MM-DD), currency (ISO), subtotal, tax, total, category (meals/travel/software/office/utilities/other), line_items [{description, quantity, amount}] from this receipt. Reply with JSON only. Use null for missing values." - Parse JSON (Edit Fields):
output(Object) ={{ JSON.parse($json.content.replace(/```json|```/g, '')) }}. The exact output field name depends on the node version; check its output panel.
Clean Receipt reads $json.output, so both branches feed it unchanged.
Exercise 3
Add a Gmail Trigger: poll every minute, Filters → Label IDs: your Receipts label, Simplify off, Options → Download Attachments on. Connect it to Has PDF?. The expression Object.keys($binary).find(...) picks attachment_0 automatically.
Two fixes for the email path: Dry Run? reads $('Receipt API'), which didn't run. Change it to {{ $('Receipt API').isExecuted && $('Receipt API').item.json.query.dry_run === 'true' }}, and route the final Respond only for API calls (the Called via API? pattern from Module 02).