This workflow corresponds to n8n.io template #14573 — we link there as the canonical source.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "jjHhqwplpaGh6V3b",
"name": "Extract invoice data from PDF with Claude AI and create vendor bill in Odoo 18",
"tags": [],
"nodes": [
{
"id": "0bd4b266-d85d-40c5-bbea-278d23dba904",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-976,
768
],
"parameters": {
"color": "#FFF5D6",
"width": 940,
"height": 1072,
"content": "## Extract Invoice Data from PDF with Claude AI and Create Vendor Bill in Odoo 18\n\nThis workflow automates **accounts payable**: upload a PDF invoice, let Claude AI extract the key fields, and automatically create a vendor bill (incoming invoice) in Odoo 18.\n\n## Who is this for?\nSmall and medium businesses using Odoo 18 who want to automate invoice data entry. Finance teams, bookkeepers, or anyone processing incoming invoices manually.\n\n## How it works\n1. Receive a PDF invoice via webhook (file upload)\n2. Convert the PDF to base64 for AI processing\n3. Claude AI extracts: vendor name, invoice number, date, amounts, tax rate\n4. Authenticate with Odoo 18 via JSON-RPC API\n5. Check for duplicate invoices (by invoice number)\n6. Find or create the vendor (res.partner) in Odoo\n7. Create a **draft** vendor bill (account.move) in Odoo\n8. Return a structured JSON response with the result\n\nThe invoice is created in **draft status** so you can review and confirm it manually in Odoo before posting.\n\n## Extracted fields\n`vendor`, `invoice_number`, `invoice_date`, `total_amount`, `currency`, `net_amount`, `tax_rate`, `tax_amount`\n\n## Setup\n- [ ] Open the **Configuration** node and enter your Odoo URL, database, user, and password\n- [ ] Create an **HTTP Header Auth** credential named `Anthropic API Key` with header name `x-api-key` and your Anthropic API key as value\n- [ ] Activate the workflow and send a test PDF\n\n## Test with curl\n```\ncurl -X POST https://your-n8n-instance.com/webhook/invoice-process -F \"data=@invoice.pdf\"\n```\n\n## Requirements\n- Odoo 18 with Invoicing module installed\n- Anthropic API key (Claude Sonnet 4.5 recommended)\n- n8n 2.x (self-hosted or cloud)\n\n## How to customize\n- Change the AI model in the **Configuration** node (e.g. `claude-haiku-4-5-20251001` for lower cost)\n- Modify the extraction prompt in **Prepare Claude Request** to add custom fields\n- Add a file storage step after invoice creation (e.g. Google Drive, S3, WebDAV)\n- Connect an email trigger instead of the webhook for fully automated processing\n\n## Note on data privacy\nThis workflow sends PDF documents to the Anthropic API for data extraction. Ensure you have a [Data Processing Agreement (DPA)](https://www.anthropic.com/legal/data-processing-addendum) with Anthropic and comply with your local data protection regulations (e.g. GDPR) before processing documents containing personal data.\n\n**Author:** Florian Eiche \u2014 [eiche-digital.de](https://eiche-digital.de)\n"
},
"typeVersion": 1
},
{
"id": "c3c7da18-0bac-49d8-9562-f768a3b4b244",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
1232
],
"parameters": {
"color": "#737373",
"width": 1340,
"height": 312,
"content": "## 1. PDF Upload & AI Extraction\nThe webhook receives a PDF file upload. The file is converted to base64, then sent to Claude AI with a structured extraction prompt. Claude returns JSON with all invoice fields."
},
"typeVersion": 1
},
{
"id": "45f83c7f-4d07-47f9-945d-bc6a24de23e1",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1360,
1232
],
"parameters": {
"color": "#737373",
"width": 1172,
"height": 312,
"content": "## 2. Odoo Authentication & Duplicate Check\nAuthenticate via JSON-RPC, then search for existing invoices with the same reference number. If a duplicate is found, the workflow returns a clean response instead of creating a duplicate."
},
"typeVersion": 1
},
{
"id": "10c75ce5-79a5-4600-a7c8-0d20f7b327a2",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2544,
1232
],
"parameters": {
"color": "#737373",
"width": 1404,
"height": 568,
"content": "## 3. Vendor Lookup & Creation\nSearch for the vendor by name in Odoo. If found, use the existing partner. If not, create a new vendor (res.partner) with `supplier_rank: 1`."
},
"typeVersion": 1
},
{
"id": "b75880da-c3df-4e65-b7cb-d366d1da377b",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
3968,
1232
],
"parameters": {
"color": "#737373",
"width": 1044,
"height": 568,
"content": "## 4. Invoice Creation & Response\nCreate a draft vendor bill (account.move with move_type `in_invoice`) in Odoo. The invoice is created with one line containing the net amount. Returns a JSON response with the Odoo invoice ID."
},
"typeVersion": 1
},
{
"id": "bf398631-cba8-45af-9934-38ef17a3d88c",
"name": "Receive Invoice PDF",
"type": "n8n-nodes-base.webhook",
"position": [
64,
1376
],
"parameters": {
"path": "invoice-process",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "057afa8e-248a-452e-8894-96ece1c0a752",
"name": "PDF to Base64",
"type": "n8n-nodes-base.extractFromFile",
"position": [
320,
1376
],
"parameters": {
"options": {},
"operation": "binaryToPropery"
},
"typeVersion": 1
},
{
"id": "eb9426eb-cc78-473c-9bab-21aa3a736b99",
"name": "Configuration",
"type": "n8n-nodes-base.set",
"position": [
592,
1376
],
"parameters": {
"mode": "raw",
"options": {},
"jsonOutput": "{\n \"odooUrl\": \"https://your-odoo-instance.com\",\n \"odooDb\": \"your_database_name\",\n \"odooUser\": \"admin\",\n \"odooPassword\": \"your_password_here\",\n \"anthropicModel\": \"claude-sonnet-4-5-20250929\"\n}"
},
"typeVersion": 3.4
},
{
"id": "67de80c2-861b-4127-8f1e-bd5cc6b3a6d4",
"name": "Prepare Claude Request",
"type": "n8n-nodes-base.code",
"position": [
864,
1376
],
"parameters": {
"jsCode": "// Build Anthropic API request with PDF as base64 document\nconst base64Item = $('PDF to Base64').first().json;\nconst pdfBase64 = base64Item.data;\n\nif (!pdfBase64 || pdfBase64.length < 100) {\n throw new Error('No valid PDF data received. Length: ' + (pdfBase64 ? pdfBase64.length : 0));\n}\n\nconst config = $('Configuration').first().json;\n\nconst requestBody = {\n model: config.anthropicModel || 'claude-sonnet-4-5-20250929',\n max_tokens: 1024,\n messages: [{\n role: 'user',\n content: [\n {\n type: 'document',\n source: {\n type: 'base64',\n media_type: 'application/pdf',\n data: pdfBase64\n }\n },\n {\n type: 'text',\n text: 'Extract the following fields from this invoice and return ONLY valid JSON, no markdown, no explanation:\\n\\n{\\n \"vendor\": \"Name of the vendor/sender\",\\n \"invoice_number\": \"Invoice number\",\\n \"invoice_date\": \"YYYY-MM-DD\",\\n \"total_amount\": 0.00,\\n \"currency\": \"EUR\",\\n \"net_amount\": 0.00,\\n \"tax_rate\": 19,\\n \"tax_amount\": 0.00\\n}\\n\\nIf a field is not found, set it to null.'\n }\n ]\n }]\n};\n\nreturn [{ json: { requestBody } }];"
},
"typeVersion": 2
},
{
"id": "5fba2aef-0212-4817-b5d4-c89e9011ee95",
"name": "Claude AI Extract",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
1152,
1376
],
"parameters": {
"url": "https://api.anthropic.com/v1/messages",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.requestBody) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": {
"parameters": [
{
"name": "anthropic-version",
"value": "2023-06-01"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "4fffd0e5-cbd0-4ecf-a400-b4bdf41d29c8",
"name": "Parse Extraction",
"type": "n8n-nodes-base.code",
"position": [
1392,
1376
],
"parameters": {
"jsCode": "// Parse and validate Claude AI response\nconst response = $input.first().json;\n\nif (response.error) {\n throw new Error('Claude API error: ' + (response.error.message || JSON.stringify(response.error)));\n}\n\nconst text = response.content?.[0]?.text || '';\n\nlet invoice;\ntry {\n const cleaned = text.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n invoice = JSON.parse(cleaned);\n} catch (e) {\n throw new Error('Claude response is not valid JSON: ' + text.substring(0, 300));\n}\n\n// Validate required fields\nconst required = ['vendor', 'invoice_number', 'invoice_date', 'total_amount'];\nconst missing = required.filter(f => !invoice[f] && invoice[f] !== 0);\nif (missing.length > 0) {\n throw new Error('Missing required fields: ' + missing.join(', '));\n}\n\n// Normalize numeric values\ninvoice.total_amount = parseFloat(invoice.total_amount);\ninvoice.net_amount = invoice.net_amount ? parseFloat(invoice.net_amount) : invoice.total_amount;\ninvoice.tax_amount = invoice.tax_amount ? parseFloat(invoice.tax_amount) : 0;\ninvoice.tax_rate = invoice.tax_rate ? parseFloat(invoice.tax_rate) : 0;\ninvoice.currency = invoice.currency || 'EUR';\n\nreturn [{ json: invoice }];"
},
"typeVersion": 2
},
{
"id": "3011122b-d5df-4492-a4ae-9703aed98373",
"name": "Odoo Authenticate",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
1648,
1376
],
"parameters": {
"url": "={{ $('Configuration').first().json.odooUrl }}/jsonrpc",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ jsonrpc: '2.0', method: 'call', params: { service: 'common', method: 'authenticate', args: [$('Configuration').first().json.odooDb, $('Configuration').first().json.odooUser, $('Configuration').first().json.odooPassword, {}] }, id: 1 }) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "72c9312e-7ed7-4131-a1d5-b342cf5b3386",
"name": "Prepare Duplicate Check",
"type": "n8n-nodes-base.code",
"position": [
1904,
1376
],
"parameters": {
"jsCode": "// Verify Odoo auth and prepare duplicate check\nconst authResult = $input.first().json;\nif (!authResult.result || authResult.error) {\n throw new Error('Odoo authentication failed: ' + JSON.stringify(authResult.error || 'uid is null'));\n}\n\nconst uid = authResult.result;\nconst config = $('Configuration').first().json;\nconst invoice = $('Parse Extraction').first().json;\n\n// Search for existing invoice with same reference number\nconst dupCheckBody = {\n jsonrpc: '2.0',\n method: 'call',\n params: {\n service: 'object',\n method: 'execute_kw',\n args: [\n config.odooDb,\n uid,\n config.odooPassword,\n 'account.move',\n 'search_read',\n [[['ref', '=', invoice.invoice_number], ['move_type', '=', 'in_invoice']]],\n { fields: ['id', 'ref', 'partner_id', 'state', 'amount_total'], limit: 1 }\n ]\n },\n id: 10\n};\n\nreturn [{ json: { uid, dupCheckBody, invoice } }];"
},
"typeVersion": 2
},
{
"id": "f3fe614e-b5d0-4a65-bf9d-124db82c9384",
"name": "Search Duplicate Invoice",
"type": "n8n-nodes-base.httpRequest",
"position": [
2144,
1376
],
"parameters": {
"url": "={{ $('Configuration').first().json.odooUrl }}/jsonrpc",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.dupCheckBody) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "50f05a19-ad7d-4596-befd-5612e8781bba",
"name": "Duplicate Found?",
"type": "n8n-nodes-base.if",
"position": [
2384,
1376
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true
},
"combinator": "and",
"conditions": [
{
"id": "condition-duplicate-exists",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.result.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "ebbb50ce-d57a-4876-b44f-4557ef378cd5",
"name": "Handle Duplicate",
"type": "n8n-nodes-base.code",
"position": [
2624,
1376
],
"parameters": {
"jsCode": "// Duplicate found \u2014 return clean response\nconst dupResult = $('Search Duplicate Invoice').first().json.result[0];\nconst invoice = $('Prepare Duplicate Check').first().json.invoice;\n\nreturn [{\n json: {\n success: false,\n duplicate: true,\n message: 'Invoice ' + invoice.invoice_number + ' already exists in Odoo.',\n existing_invoice: {\n odoo_id: dupResult.id,\n status: dupResult.state,\n amount: dupResult.amount_total,\n vendor: dupResult.partner_id ? dupResult.partner_id[1] : null\n },\n extracted_data: invoice\n }\n}];"
},
"typeVersion": 2
},
{
"id": "abac0d06-aaea-43d7-bf2c-3cc833de45a0",
"name": "Prepare Vendor Search",
"type": "n8n-nodes-base.code",
"position": [
2624,
1536
],
"parameters": {
"jsCode": "// No duplicate \u2014 prepare vendor search\nconst prev = $('Prepare Duplicate Check').first().json;\nconst config = $('Configuration').first().json;\n\nconst searchBody = {\n jsonrpc: '2.0',\n method: 'call',\n params: {\n service: 'object',\n method: 'execute_kw',\n args: [\n config.odooDb,\n prev.uid,\n config.odooPassword,\n 'res.partner',\n 'search_read',\n [[['name', 'ilike', prev.invoice.vendor], ['supplier_rank', '>', 0]]],\n { fields: ['id', 'name'], limit: 1 }\n ]\n },\n id: 2\n};\n\nreturn [{ json: { uid: prev.uid, searchBody, invoice: prev.invoice } }];"
},
"typeVersion": 2
},
{
"id": "eaff093f-d675-4a21-bd52-76df146eb9b6",
"name": "Search Vendor",
"type": "n8n-nodes-base.httpRequest",
"position": [
2864,
1536
],
"parameters": {
"url": "={{ $('Configuration').first().json.odooUrl }}/jsonrpc",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.searchBody) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "daeb5b81-3552-4797-bfa7-0b1bc8c26d1b",
"name": "Vendor Exists?",
"type": "n8n-nodes-base.if",
"position": [
3104,
1376
],
"parameters": {
"options": {},
"conditions": {
"options": {
"leftValue": "",
"caseSensitive": true
},
"combinator": "and",
"conditions": [
{
"id": "condition-vendor-exists",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $json.result.length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2
},
{
"id": "ffd976ba-0a8c-4774-9adf-592736a0fcfd",
"name": "Use Existing Vendor",
"type": "n8n-nodes-base.code",
"position": [
3344,
1360
],
"parameters": {
"jsCode": "// Use existing vendor\nconst partnerId = $json.result[0].id;\nconst partnerName = $json.result[0].name;\nconst prev = $('Prepare Vendor Search').first().json;\n\nreturn [{\n json: {\n partnerId,\n partnerName,\n uid: prev.uid,\n invoice: prev.invoice\n }\n}];"
},
"typeVersion": 2
},
{
"id": "c34dd409-5f06-4417-8cf4-e3537d607158",
"name": "Prepare Create Vendor",
"type": "n8n-nodes-base.code",
"position": [
3344,
1488
],
"parameters": {
"jsCode": "// Vendor not found \u2014 prepare creation\nconst config = $('Configuration').first().json;\nconst prev = $('Prepare Vendor Search').first().json;\n\nconst createBody = {\n jsonrpc: '2.0',\n method: 'call',\n params: {\n service: 'object',\n method: 'execute_kw',\n args: [\n config.odooDb,\n prev.uid,\n config.odooPassword,\n 'res.partner',\n 'create',\n [{ name: prev.invoice.vendor, supplier_rank: 1, company_type: 'company' }]\n ]\n },\n id: 3\n};\n\nreturn [{ json: { createBody, uid: prev.uid, invoice: prev.invoice } }];"
},
"typeVersion": 2
},
{
"id": "29db158d-d227-450f-a8e0-53eaa894a00f",
"name": "Create Vendor in Odoo",
"type": "n8n-nodes-base.httpRequest",
"position": [
3584,
1488
],
"parameters": {
"url": "={{ $('Configuration').first().json.odooUrl }}/jsonrpc",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.createBody) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "25137c82-5c80-4a18-82c0-9011da543f5e",
"name": "Get New Vendor ID",
"type": "n8n-nodes-base.code",
"position": [
3792,
1488
],
"parameters": {
"jsCode": "// Extract new vendor ID from create response\nconst result = $input.first().json;\nif (!result.result) {\n throw new Error('Failed to create vendor: ' + JSON.stringify(result.error || result));\n}\n\nconst prev = $('Prepare Create Vendor').first().json;\n\nreturn [{\n json: {\n partnerId: result.result,\n partnerName: prev.invoice.vendor,\n uid: prev.uid,\n invoice: prev.invoice\n }\n}];"
},
"typeVersion": 2
},
{
"id": "a33d9edb-8a89-413e-8e64-8fd330a2720a",
"name": "Prepare Invoice",
"type": "n8n-nodes-base.code",
"position": [
4048,
1360
],
"parameters": {
"jsCode": "// Prepare Odoo vendor bill (account.move)\nconst data = $input.first().json;\nconst config = $('Configuration').first().json;\n\nconst invoiceBody = {\n jsonrpc: '2.0',\n method: 'call',\n params: {\n service: 'object',\n method: 'execute_kw',\n args: [\n config.odooDb,\n data.uid,\n config.odooPassword,\n 'account.move',\n 'create',\n [{\n move_type: 'in_invoice',\n partner_id: data.partnerId,\n ref: data.invoice.invoice_number,\n invoice_date: data.invoice.invoice_date,\n invoice_line_ids: [\n [0, 0, {\n name: 'Invoice ' + data.invoice.invoice_number + ' - ' + data.invoice.vendor,\n quantity: 1,\n price_unit: data.invoice.net_amount\n }]\n ]\n }]\n ]\n },\n id: 4\n};\n\nreturn [{\n json: {\n invoiceBody,\n partnerId: data.partnerId,\n partnerName: data.partnerName,\n uid: data.uid,\n invoice: data.invoice\n }\n}];"
},
"typeVersion": 2
},
{
"id": "696ad174-97fd-493f-acee-5475b99ca5be",
"name": "Create Invoice in Odoo",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
4288,
1504
],
"parameters": {
"url": "={{ $('Configuration').first().json.odooUrl }}/jsonrpc",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify($json.invoiceBody) }}",
"sendBody": true,
"specifyBody": "json"
},
"typeVersion": 4.2
},
{
"id": "19a84d9f-9c13-4ed6-942a-5118bb66a8aa",
"name": "Build Response",
"type": "n8n-nodes-base.code",
"position": [
4528,
1360
],
"parameters": {
"jsCode": "// Verify invoice creation and build success response\nconst result = $input.first().json;\nif (!result.result) {\n throw new Error('Failed to create invoice: ' + JSON.stringify(result.error || result));\n}\n\nconst prev = $('Prepare Invoice').first().json;\n\nreturn [{\n json: {\n success: true,\n odoo_invoice_id: result.result,\n partner_id: prev.partnerId,\n partner_name: prev.partnerName,\n invoice: prev.invoice\n }\n}];"
},
"typeVersion": 2
},
{
"id": "3ccdd767-939e-4d60-b867-71eb57bac30e",
"name": "Success Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
4768,
1360
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"typeVersion": 1
},
{
"id": "dcedaa35-36c5-4ac0-b990-caf9cc6e7b72",
"name": "Duplicate Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
2864,
1376
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify($json) }}"
},
"typeVersion": 1
},
{
"id": "d511e2fd-8ca9-441c-8c16-9059f6170ae3",
"name": "Error Response",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1808,
1744
],
"parameters": {
"options": {
"responseCode": 500
},
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ success: false, error: $json.error?.message || $json.message || 'Unknown error', node: $json.node || 'unknown' }) }}"
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "9e93180c-b647-4acd-a020-827012ae57ec",
"connections": {
"Configuration": {
"main": [
[
{
"node": "Prepare Claude Request",
"type": "main",
"index": 0
}
]
]
},
"PDF to Base64": {
"main": [
[
{
"node": "Configuration",
"type": "main",
"index": 0
}
]
]
},
"Search Vendor": {
"main": [
[
{
"node": "Vendor Exists?",
"type": "main",
"index": 0
}
]
]
},
"Build Response": {
"main": [
[
{
"node": "Success Response",
"type": "main",
"index": 0
}
]
]
},
"Vendor Exists?": {
"main": [
[
{
"node": "Use Existing Vendor",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Create Vendor",
"type": "main",
"index": 0
}
]
]
},
"Prepare Invoice": {
"main": [
[
{
"node": "Create Invoice in Odoo",
"type": "main",
"index": 0
}
]
]
},
"Duplicate Found?": {
"main": [
[
{
"node": "Handle Duplicate",
"type": "main",
"index": 0
}
],
[
{
"node": "Prepare Vendor Search",
"type": "main",
"index": 0
}
]
]
},
"Handle Duplicate": {
"main": [
[
{
"node": "Duplicate Response",
"type": "main",
"index": 0
}
]
]
},
"Parse Extraction": {
"main": [
[
{
"node": "Odoo Authenticate",
"type": "main",
"index": 0
}
]
]
},
"Claude AI Extract": {
"main": [
[
{
"node": "Parse Extraction",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Get New Vendor ID": {
"main": [
[
{
"node": "Prepare Invoice",
"type": "main",
"index": 0
}
]
]
},
"Odoo Authenticate": {
"main": [
[
{
"node": "Prepare Duplicate Check",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Receive Invoice PDF": {
"main": [
[
{
"node": "PDF to Base64",
"type": "main",
"index": 0
}
]
]
},
"Use Existing Vendor": {
"main": [
[
{
"node": "Prepare Invoice",
"type": "main",
"index": 0
}
]
]
},
"Create Vendor in Odoo": {
"main": [
[
{
"node": "Get New Vendor ID",
"type": "main",
"index": 0
}
]
]
},
"Prepare Create Vendor": {
"main": [
[
{
"node": "Create Vendor in Odoo",
"type": "main",
"index": 0
}
]
]
},
"Prepare Vendor Search": {
"main": [
[
{
"node": "Search Vendor",
"type": "main",
"index": 0
}
]
]
},
"Create Invoice in Odoo": {
"main": [
[
{
"node": "Build Response",
"type": "main",
"index": 0
}
],
[
{
"node": "Error Response",
"type": "main",
"index": 0
}
]
]
},
"Prepare Claude Request": {
"main": [
[
{
"node": "Claude AI Extract",
"type": "main",
"index": 0
}
]
]
},
"Prepare Duplicate Check": {
"main": [
[
{
"node": "Search Duplicate Invoice",
"type": "main",
"index": 0
}
]
]
},
"Search Duplicate Invoice": {
"main": [
[
{
"node": "Duplicate Found?",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow automates accounts payable: upload a PDF invoice, let Claude AI extract the key fields, and automatically create a vendor bill (incoming invoice) in Odoo 18.
Source: https://n8n.io/workflows/14573/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di
This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .
This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c
Sign PDF documents with legally-compliant digital signatures using X.509 certificates. Supports multiple PAdES signature levels (B, T, LT, LTA) with optional visible stamps.
📡 This workflow serves as the central Alpha Vantage API fetcher for Tesla trading indicators, delivering cleaned 20-point JSON outputs for three timeframes: , , and . It is required by the following a