{
"parameters": {
"authentication": "serviceAccount",
"event": "fileCreated",
"options": {
"folderToWatch": {
"__rl": true,
"mode": "id",
"value": "1xJ_REDACTED_INVOICES_INBOX_FOLDER_ID"
},
"fileType": "application/pdf"
},
"pollTimes": {
"item": [
{ "mode": "everyMinute" }
]
}
},
"name": "New PDF in Inbox",
"type": "n8n-nodes-base.googleDriveTrigger",
"typeVersion": 1,
"position": [240, 300]
}
Service-account auth, not OAuth2. The folder is shared with the bot's email (looks like n8nbot@yourproject.iam.gserviceaccount.com). Polling every minute is generous; we tested every 30 seconds and saw no Drive API rate-limit issues at this volume. The fileType filter ensures we only trigger on PDFs — vendors occasionally email DOCX invoices, those go to a separate manual-review folder.
### Node 2 — Claude Sonnet 4.5 Vision (HTTP Request)
{
"parameters": {
"method": "POST",
"url": "https://api.anthropic.com/v1/messages",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{ "name": "x-api-key", "value": "={{ $credentials.anthropicApi.apiKey }}" },
{ "name": "anthropic-version", "value": "2023-06-01" },
{ "name": "content-type", "value": "application/json" }
]
},
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"model\": \"claude-sonnet-4-5\",\n \"max_tokens\": 1500,\n \"system\": \"You are an Indian B2B invoice extractor. Output STRICT JSON ONLY matching this schema: { vendor_name (string), vendor_gstin (string, 15 chars), invoice_no (string), invoice_date (YYYY-MM-DD), state_code (2-digit), lines: [{description, hsn_code, qty, rate, taxable_value, gst_pct, gst_amount}], subtotal (number), cgst (number), sgst (number), igst (number), total (number) }. Rules: do not invent values. If a field is missing, set null. invoice_date in ISO format. All amounts in INR, no thousand separators.\",\n \"messages\": [{\n \"role\": \"user\",\n \"content\": [\n { \"type\": \"document\", \"source\": { \"type\": \"base64\", \"media_type\": \"application/pdf\", \"data\": \"={{ $binary.data.data }}\" } },\n { \"type\": \"text\", \"text\": \"Extract this invoice into the schema. Return JSON only.\" }\n ]\n }]\n}"
},
"name": "Claude Vision extract",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [680, 300]
}
Three things matter. The schema is in the system message, not the user message — Claude follows system instructions more reliably. The PDF is sent as a document content block (base64); Claude Vision handles multi-page PDFs natively, no need to split into images. max_tokens 1500 is enough for a 20-line invoice; bump to 3000 if your vendors send long itemised PDFs. Cost at this volume: roughly ₹2.40 per invoice (4k input tokens for a 2-page PDF, 800 output tokens for the JSON).
### Node 3 — JSON Schema + math validation (Code node)
{
"parameters": {
"jsCode": "const Ajv = require('ajv');\nconst ajv = new Ajv({ allErrors: true });\nconst schema = {\n type: 'object',\n required: ['vendor_name','vendor_gstin','invoice_no','invoice_date','state_code','lines','subtotal','total'],\n properties: {\n vendor_name: { type: 'string', minLength: 2 },\n vendor_gstin: { type: 'string', pattern: '^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[0-9A-Z]{1}Z[0-9A-Z]{1}$' },\n invoice_no: { type: 'string', minLength: 1 },\n invoice_date: { type: 'string', pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' },\n state_code: { type: 'string', pattern: '^[0-9]{2}$' },\n lines: { type: 'array', minItems: 1, items: { type: 'object', required: ['description','qty','rate','taxable_value','gst_pct'] } },\n subtotal: { type: 'number' },\n total: { type: 'number' }\n }\n};\nconst validate = ajv.compile(schema);\n\nconst items = $input.all();\nconst out = [];\nfor (const it of items) {\n // Parse Claude response\n let data;\n try {\n const text = it.json.content[0].text;\n const jsonStart = text.indexOf('{');\n data = JSON.parse(text.slice(jsonStart));\n } catch (e) {\n out.push({ json: { ...it.json, _valid: false, _reason: 'unparseable_json', _error: e.message } });\n continue;\n }\n // Schema check\n const valid = validate(data);\n if (!valid) {\n out.push({ json: { ...data, _valid: false, _reason: 'schema_fail', _errors: validate.errors } });\n continue;\n }\n // Math check: lines sum to subtotal\n const lineSum = data.lines.reduce((s,l) => s + Number(l.taxable_value || 0), 0);\n if (Math.abs(lineSum - data.subtotal) > 1) {\n out.push({ json: { ...data, _valid: false, _reason: 'subtotal_mismatch', _expected: lineSum, _got: data.subtotal } });\n continue;\n }\n // Math check: subtotal + GST = total\n const gst = Number(data.cgst||0) + Number(data.sgst||0) + Number(data.igst||0);\n if (Math.abs(data.subtotal + gst - data.total) > 1) {\n out.push({ json: { ...data, _valid: false, _reason: 'total_mismatch', _expected: data.subtotal + gst, _got: data.total } });\n continue;\n }\n out.push({ json: { ...data, _valid: true } });\n}\nreturn out;"
},
"name": "Validate extraction",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [900, 300]
}
Three validations stack. (1) Schema validation via Ajv — catches missing fields and bad formats (GSTIN regex is the standard 15-character pattern). (2) Subtotal math check — line items must sum to the subtotal within ₹1 (rounding tolerance). (3) Total math check — subtotal + GST must equal total within ₹1. Any failure routes to HITL. We chose ₹1 tolerance after seeing rounding artefacts in vendor invoices that printed ₹1,247.50 as ₹1,248. Tolerances tighter than ₹1 cause too many false-positive HITL trips; looser tolerances let real errors slip through.
## The 17-node graph
- 1. Google Drive Trigger (new PDF in Invoices Inbox)
- 2. Google Drive download — file as binary
- 3. Wait — 2 sec (lets Drive metadata settle, prevents race condition)
- 4. HTTP — Claude Sonnet 4.5 Vision extract
- 5. Code — JSON Schema + math validation
- 6. IF — _valid true vs false branch
- 7a. HTTP — Tally vendor master lookup (validate vendor exists)
- 8a. HTTP — Tally Purchase voucher POST (XML)
- 9a. XML — parse Tally response, check for LINEERROR
- 10a. Google Drive move — to "Posted" folder, rename with vendor + date prefix
- 11a. Sheets append — success log row
- 7b. Google Drive move — to "Needs Review" folder (failure branch)
- 8b. Sheets append — exception log with extracted JSON
- 9b. Slack DM — accountant with link to PDF + JSON for one-click correction
- 12. Wait — 24h then re-validate (in case accountant fixed the JSON)
- 13. Sub-flow — re-post if accountant approved correction
- 14. Error workflow trigger — DM ops on any node failure
Invoice flagged for review:
File: Vendor_ABC_Sept_142.pdf [link]
Reason: subtotal_mismatch
Expected: ₹14,820 | Got: ₹14,790
Extracted JSON:
{
"vendor_name": "ABC Chemicals Pvt Ltd",
"vendor_gstin": "27AABCA1234B1Z5",
"invoice_no": "INV-2025-0142",
...
}
Reply with corrected JSON or "skip" to defer.
The accountant edits the JSON in Slack (we use a thread for the back-and-forth), our bot picks up the corrected JSON via a slash command and re-runs the post-validation steps. Round-trip per exception: 90 seconds median. Saves us building a dedicated review UI.
- Google service-account JSON pasted into n8n; folder shared with bot email
- Three Drive folders created: Invoices Inbox, Posted, Needs Review — each shared with bot
- Anthropic API key stored as n8n credential, monthly cap set on the dashboard
- JSON Schema reviewed with accountant; all required fields confirmed
- Tally vendor master synced — every active vendor has a Ledger entry under Sundry Creditors
- State code mapping table verified (29 states + 8 UTs)
- Slack bot has chat:write and im:write scopes for HITL DMs
- Test invoice run end-to-end on a sandbox Tally company before pointing at production
- Workflow exported to git as JSON the day of go-live
- Accountant briefed on the Slack thread workflow for corrections
{. We tried "respond with JSON only no markdown" in the prompt but it occasionally still wraps. Belt and braces.
Symptom: "Invoices with handwritten amounts fail extraction." Cause: Vision is good but not perfect on handwriting. Fix: route handwritten invoices straight to the HITL queue — the workflow flags them via a Code node check on whether the PDF has a high ratio of low-confidence text regions (proxied by short field values from extraction). Fast triage.
Symptom: "Vendor GSTIN extracted but does not match Tally master." Cause: typo in the vendor's invoice or in Tally. Fix: fuzzy-match on vendor name + GSTIN prefix (first 2 digits = state). Three-tier match: exact GSTIN, name+state match, name-only match. If only name matches, route to HITL.
Symptom: "Workflow times out on a 12-page scanned invoice." Cause: Vision call takes longer for large PDFs. Fix: increase HTTP timeout in n8n to 90 seconds. Also: scanned invoices are bigger and burn more tokens — we cap the max PDF size at 5 MB and route bigger files to manual review.
Symptom: "Posted invoices show wrong dates in Tally." Cause: ISO date YYYY-MM-DD vs Tally DD-MM-YYYY format mismatch. Fix: explicit conversion in the voucher builder before sending to Tally — we use moment(date).format('YYYYMMDD') as Tally expects compact YYYYMMDD format in the XML.
## Mini case study — 6 weeks at the Pune procurement team
The first install ran on 8 September 2025. After 6 weeks: 2,040 invoices processed, 1,856 auto-posted (91%), 184 routed to HITL. Of the 184 HITL invoices, 162 were corrected and re-posted within an hour, 18 needed vendor follow-up (truly bad source data — wrong totals on the invoice itself), 4 were rejected as fraudulent (vendor not in master and GSTIN failed validation). The accountant's monthly time on data entry: down from 22 hours to 2.4 hours. Monthly close moved from the 15th to the 8th — the procurement manager now uses the saved time on supplier negotiations.
For a related pattern in a different domain, see our Razorpay + Tally daily reconciliation from 4 September 2025 — same Tally backbone, different source system. For the 4-way sales-side equivalent, see the Shopify + Razorpay + Tally Diwali sync shipped earlier in October.
## When NOT to build this
Skip this if (a) your invoice volume is under 60/month — manual entry plus a calculator is genuinely cheaper than the LLM cost + setup time, (b) your vendors send mostly Excel or DOCX invoices — Vision is built for image-heavy formats, structured spreadsheets are easier to parse traditionally, or (c) your accountant is not on Slack — the HITL pattern depends on a fast async correction loop and email is too slow. We turned down one client in 2025 for reason (c). Email-based corrections took 4-6 hours per round trip; the workflow's value disappeared.
For a related "human-in-the-loop" pattern in a different domain, see our TalkDrill case — same architecture (LLM extract → schema validate → human review queue), applied to user-submitted speech feedback instead of invoices. As Hrishikesh, our CTO, points out, the LLM does the boring 91%; the accountant focuses on the interesting 9% that actually needs judgment.
## FAQ
### How long to build and deploy this n8n + Claude Vision invoice OCR pipeline?
For us, 12 working days end-to-end: 1 day on Drive folder setup + service account, 2 days on Vision prompt iteration with the accountant (vendor-specific quirks), 3 days on validation logic (schema + math), 2 days on Tally Purchase voucher mapping, 2 days on Slack HITL bot, 2 days on shadow run with the accountant.
### What accuracy can I expect from Claude Sonnet 4.5 Vision on Indian invoices?
In our 2,040-invoice production sample: 96% field-level extraction accuracy, 91% straight-through (passed all three validations), 9% routed to HITL. Handwritten amounts and stamp-overlay invoices are the main failure modes. For comparison, AWS Textract scored 78% straight-through on the same sample.
### How does the math validation actually catch errors?
Three checks. Sum of line item taxable values must equal subtotal within ₹1. Subtotal + (CGST + SGST + IGST) must equal total within ₹1. Any line item's taxable_value times (1 + gst_pct/100) should equal gross within ₹1. Together these catch ~95% of extraction errors that schema validation alone misses.
### Can I use GPT-4o Vision instead of Claude Sonnet 4.5?
Yes. We tested both. Sonnet 4.5 won on Hindi/Marathi vendor names (slightly better Devanagari OCR) and on multi-page invoices. GPT-4o won on table-heavy invoices with merged cells. Choose based on your invoice mix; cost is comparable.
### What if the LLM extracts the wrong vendor name?
The vendor master lookup catches this — we fuzzy-match on GSTIN (which is far harder to mis-extract than a name). If GSTIN matches but name does not, we trust GSTIN. If neither matches, route to HITL.
### How do I prevent the same invoice being processed twice?
The workflow moves the PDF out of "Inbox" to either "Posted" or "Needs Review" before the next trigger fires. We also store a hash (SHA-256 of file contents) in a Sheets ledger; the trigger node checks the hash before processing. Duplicates skip silently.
### Can the accountant edit the extracted JSON before posting?
Yes, that is exactly the HITL pattern. The Slack DM contains the extracted JSON. The accountant edits in Slack, replies with corrected JSON, our bot re-runs validation and posts if it passes. Round-trip is 90 seconds median.
Want this invoice-extraction pipeline for your finance team?
We ship the Drive + Claude Vision + Tally invoice flow — schema validation, math checks, Slack HITL queue, vendor master sync — in 12 working days for ₹78,000. Suitable for any procurement team handling 80+ invoices per month with Tally Server-9 on the LAN.
Book a 20-min Call
