AI Invoice Processing System in n8n: Full OCR + OpenAI Guide
Published on Clarity With AI | By Muhammad Faisal Gurmani, CA Finalist
During my articleship at Zahid Jameel & Co., I have reviewed enough accounts payable files to know exactly where invoice processing breaks down for small and mid-size firms. It is almost never the accounting rules that trip people up. It is the manual entry: someone retypes a vendor name slightly wrong, a duplicate PDF slips through because it was renamed before re-upload, or a scanned invoice gets keyed in with the wrong total because the resolution was poor. I wanted to see whether I could build something that catches these problems before they ever reach a ledger, without paying for an enterprise AP automation platform that most small firms cannot justify.
So I built an AI invoice processing system in n8n using Google Cloud Vision for OCR and OpenAI for structured data extraction. This is really an accounts payable automation workflow that combines invoice OCR automation with AI-based invoice data extraction, and it doubles as duplicate invoice detection so nothing gets paid twice. Below is every node, every setting, and every field name exactly as I configured them, so you can rebuild this without needing to see my screen. If you already have a free n8n account (self-hosted or cloud), a Google Cloud project, and an OpenAI API key, budget an afternoon. If you have never opened n8n before, budget a full day, mostly for setting up the three API credentials.
Why I did not just buy a SaaS tool
I looked at a few off-the-shelf AP automation tools and invoice scanning software before building this. They work, but two things bothered me. First, the per-document pricing adds up fast once you are past a small monthly volume, and most small firms process far more invoices than the entry-level tiers assume. Second, the field mapping is rigid. When a client wanted tax broken out separately from line-item totals in a specific way for their state filing, the SaaS tool simply could not do it. A custom n8n workflow costs a fraction of a cent per invoice in API calls and the JSON schema is entirely mine to define.
| Feature | Off-the-shelf SaaS | Custom n8n workflow |
|---|---|---|
| Cost | Per-document fees that scale with volume | Fractions of a cent per API call |
| Data structure | Fixed fields you cannot change | Fully custom JSON schema, including line items and tax |
| Routing | Limited to built-in integrations | Goes anywhere: Slack, Postgres, your own ERP |
Step 1: Getting invoices into the workflow
Open a blank n8n canvas and add these nodes in order.
- Google Drive Trigger. Set "Event" to File Created. Under "Watch Folder," pick a folder in your Drive named Unprocessed (create it first if it does not exist). Set "Poll Times" to Every Minute. This node outputs the file ID and file name every time a new invoice lands in that folder.
- Google Drive node (second one). Set "Resource" to File, "Operation" to Download. In the "File ID" field, map it from the trigger output:
{{ $json.id }}. This converts the file into binary data under the field namedataby default, which the next nodes will reference. - Switch node. Add one condition rule checking
{{ $binary.data.fileExtension }}. Route output 0 when the value equalspdf. Route output 1 when the value equalsjpeg,jpg, orpng(use an OR condition group for the three image types). Everything downstream forks into two parallel paths from here.
I learned on an early test run that feeding both file types through the same OCR call produces garbled output for one of them, because the Vision API endpoint for standalone images is different from the endpoint for PDF documents. The Switch node above is what prevents that.
Step 2: OCR with Google Cloud Vision
Image path (JPEG/PNG):
- Add a Move Binary Data node. Set "Mode" to Binary to Property, and "Encoding" to Base64. Output field name:
base64Image. - Add an HTTP Request node. Method: POST. URL:
https://vision.googleapis.com/v1/images:annotate. Authentication: Generic Credential Type, Header Auth, using your Google API key. Body Content Type: JSON. Body:{ "requests": [ { "image": { "content": "={{ $json.base64Image }}" }, "features": [ { "type": "DOCUMENT_TEXT_DETECTION" } ] } ] }
PDF path:
- Same Move Binary Data setup, output field
base64Pdf. - HTTP Request node. Method: POST. URL:
https://vision.googleapis.com/v1/files:annotate. Body:{ "requests": [ { "inputConfig": { "content": "={{ $json.base64Pdf }}", "mimeType": "application/pdf" }, "features": [ { "type": "DOCUMENT_TEXT_DETECTION" } ] } ] } - Add a Code node right after this call. Vision returns one response object per PDF page under
responses[].fullTextAnnotation.text. Loop through the array and join every page's text into a single string:let combined = ""; for (const page of items[0].json.responses) { combined += page.fullTextAnnotation?.text || ""; } return [{ json: { extracted_text: combined } }];
For the image path, the extracted text sits at a single path: responses[0].fullTextAnnotation.text. Add a Set node to copy that value into the same field name, extracted_text, so both paths merge back together with identical field naming before Step 3.
Step 3: The duplicate check that actually matters
This is the part I spent the most time on, because catching duplicate payments is a real control, not a nice-to-have. Merge both paths from Step 2 back into one line first, using a Merge node set to Append, then run this two-stage check.
- Hash check. Add a Crypto node. Action: Hash. Type: SHA256. Property Name:
data(the original binary field from Step 1). This outputs a field calledhash. Follow it with a Postgres node, Operation: Select, queryingSELECT file_hash FROM processed_invoices WHERE file_hash = '{{ $json.hash }}'. Add an IF node checking whether the query returned any rows. If it did, route to a NoOp node labeled "Duplicate, stop" and end the branch there. - Logical check. If the hash is new, send
extracted_textto an OpenAI node (model: gpt-4o-mini) with a short prompt asking only forvendor_nameandinvoice_numberas JSON. Feed those two fields into a second Postgres Select node:SELECT id FROM processed_invoices WHERE vendor_name = '{{ $json.vendor_name }}' AND invoice_number = '{{ $json.invoice_number }}'. If a row comes back, route to a Slack node instead of the ledger, so a human confirms before anything gets rejected or approved.
I tested this against a batch of intentionally duplicated invoices, some identical files, some rescanned versions of the same document with slightly different image quality, and both stages caught what they were supposed to catch.
Step 4: Structured extraction with OpenAI
Add an OpenAI node, Resource: Text, Operation: Message a Model (or the Information Extractor sub-node if your n8n version has it), model gpt-4o. Paste the system prompt into the System field and the JSON schema into the schema or response-format field exactly as below. Map extracted_text from Step 2 as the user message content.
System prompt: You are an expert extraction algorithm for a US accounting firm. You only extract relevant information from the provided invoice text. You must strictly follow the output schema. If you do not know the value of an attribute or it is missing from the document, output null, do not guess or hallucinate values. Ensure all dates are formatted as YYYY-MM-DD.
{
"name": "extract_invoice_data",
"strict": true,
"schema": {
"type": "object",
"properties": {
"company_name": { "type": "string" },
"customer_name": { "type": "string" },
"invoice_number": { "type": "string" },
"invoice_date": { "type": "string" },
"total_amount": { "type": "number" },
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"quantity": { "type": "number" },
"unit_price": { "type": "number" },
"total": { "type": "number" }
},
"required": ["description", "total", "quantity", "unit_price"],
"additionalProperties": false
}
}
},
"required": ["company_name", "invoice_number", "invoice_date", "total_amount", "line_items"],
"additionalProperties": false
}
}
The node returns this JSON in a field called message.content as a string. Add a Set node right after it and parse that string with {{ JSON.parse($json.message.content) }} so every field below becomes directly mappable in Step 5.
If you want a broader look at where this fits into a firm's wider automation, I covered the accounts payable side of this in more depth in AI agents for accounts payable in small firms.
Step 5: Logging and cleanup
Add a Google Sheets node. Operation: Append Row. Map these columns directly from the parsed JSON: Vendor from company_name, Invoice Number from invoice_number, Date from invoice_date, Total from total_amount, Line Items from a stringified version of the line_items array, and Processed Date from {{ $now.toISO() }}.
Follow it with a Google Drive Move node targeting a folder named Processed, then an Update File node to rename it using this expression: {{ $json.invoice_date }}_{{ $json.company_name.replace(/\s+/g, "") }}_{{ $json.total_amount }}.pdf. That gives every file a name like 2026-08-15_AcmeSupplyCo_1420.pdf instead of scan001.pdf, which matters the first time you need to pull one file during an audit trail review.
Last node: an IF node right after Step 2's OCR call, checking whether extracted_text is empty or under 20 characters (a rough signal that the scan failed). If true, route to a Slack node posting to your finance channel with the file name and a link, instead of letting a blank or garbled record continue to the sheet.
What I would still check before you copy this
API pricing and free-tier limits change. Before you build this yourself, check the current Google Cloud Vision pricing page and the OpenAI pricing page rather than relying on numbers from any single tutorial, including this one. Every node name, field name, and expression above is exactly what I used, and that part will not go stale the way pricing tiers do.
If you are setting up automation like this as part of a wider month-end process, it is worth reading how I structured the broader automation stack in AI agents for bookkeeping automation in small firms, since the invoice pipeline here feeds directly into that workflow.
Frequently asked questions
Is n8n free for invoice automation?
The core n8n tool is free if you self-host it, and the cloud version has a free tier with limited executions. What actually costs money in this workflow is the Google Cloud Vision OCR calls and the OpenAI calls, and both of those run a fraction of a cent per invoice at the volumes a small firm typically processes.
Can n8n read PDF invoices directly, or do I need to convert them first?
n8n can pass a PDF straight through as binary data, but I do not send that PDF straight to an LLM. I route it through Google Cloud Vision's file annotation endpoint first to get clean OCR text, then send that text to OpenAI. Sending a raw PDF to a language model works less reliably on multi-page invoices than OCR-first extraction does.
What is the best OCR engine for invoice data extraction?
I use Google Cloud Vision because document text detection handles varied invoice layouts well and it plugs into n8n through a simple HTTP Request node. Other engines like AWS Textract or Azure Document Intelligence work too, and Textract in particular has purpose-built invoice and receipt parsing, but the setup in n8n takes more nodes to wire up correctly.
How do you stop an automation workflow from paying the same invoice twice?
That is the whole point of Step 3 above. A SHA256 hash check catches an identical file uploaded twice, and a second logical check on vendor name plus invoice number catches a rescanned copy of the same invoice that would not match on hash alone. Anything that matches gets routed to a human for review instead of straight to the ledger.
Do I need to know how to code to build this in n8n?
Most of this workflow is point-and-click node configuration. The only place I used actual code is the small Code node in Step 2 that loops through multi-page OCR responses and joins them into one string, and that snippet is included above exactly as I wrote it.
Can this connect to QuickBooks or other accounting software instead of Google Sheets?
Yes. I log to Google Sheets here because it is the simplest way to show the workflow, but the same parsed JSON output from Step 4 can be sent to a QuickBooks, Xero, or ERP node instead, or in addition, since n8n lets you branch the same data to multiple destinations from one node.
Final thoughts
This is not a system I built as a demo. It is the same architecture I would put in front of a client if they asked me to cut manual invoice entry without handing their financial data to a black-box SaaS tool. The duplicate check alone is worth building even if you skip everything else, because that is the control that actually protects a firm from double payments, and it is the one thing most off-the-shelf tools handle worse than you would expect.