{
  "name": "Invoice processing: read, check and book supplier invoices",
  "nodes": [
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyX",
              "value": 5,
              "unit": "minutes"
            }
          ]
        },
        "simple": false,
        "filters": {
          "q": "has:attachment filename:pdf to:invoices"
        },
        "options": {
          "downloadAttachments": true
        }
      },
      "id": "d932c1da-0d27-537f-97e0-eaa80415badb",
      "name": "Invoice arrives by email",
      "type": "n8n-nodes-base.gmailTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "credentials": {
        "gmailOAuth2": {
          "id": "",
          "name": "Gmail (invoices inbox)"
        }
      }
    },
    {
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "attachment_0",
        "options": {}
      },
      "id": "e38823ce-535e-5a80-9f3c-b574c2ed6f16",
      "name": "Read the PDF",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'claude-haiku-5-5', max_tokens: 600, system: \"Extract invoice data. Reply with JSON only: {\\\"supplier\\\": \\\"\\\", \\\"invoice_number\\\": \\\"\\\", \\\"invoice_date\\\": \\\"YYYY-MM-DD\\\", \\\"due_date\\\": \\\"YYYY-MM-DD\\\", \\\"currency\\\": \\\"\\\", \\\"subtotal\\\": 0, \\\"tax\\\": 0, \\\"total\\\": 0, \\\"confidence\\\": 0-1}. Use null for anything not on the invoice. Never guess numbers.\", messages: [{ role: 'user', content: ($json.text ?? '').slice(0, 12000) }] }) }}",
        "options": {
          "timeout": 60000
        }
      },
      "id": "a603298b-3ce6-5afb-84e7-d31917c23bc7",
      "name": "Extract fields with Claude",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        0
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "Anthropic API key (header x-api-key)"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Claude replies with JSON text; turn it into fields and keep the original input alongside.\nreturn $input.all().map((item) => {\n  const text = item.json.content?.[0]?.text ?? '{}';\n  const match = text.match(/\\{[\\s\\S]*\\}/);\n  let parsed = {};\n  try { parsed = JSON.parse(match ? match[0] : text); } catch { parsed = { parse_error: true, raw: text }; }\n  return { json: { ...parsed } }; // supplier, invoice_number, dates, amounts, confidence\n});"
      },
      "id": "e0f881a4-2794-56f1-bb02-bf70394fa7f0",
      "name": "Read the fields",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return $input.all().map((i) => {\n  const f = i.json;\n  const sums = f.subtotal != null && f.tax != null && f.total != null && Math.abs(f.subtotal + f.tax - f.total) < 0.02;\n  const complete = Boolean(f.supplier && f.invoice_number && f.total != null && f.due_date);\n  return { json: { ...f, ok_to_book: sums && complete && (f.confidence ?? 0) >= 0.85, reason: !complete ? 'missing fields' : !sums ? 'totals do not add up' : 'low confidence' } };\n});"
      },
      "id": "2520ab5d-fd83-5624-9b0f-36ac7804cb7a",
      "name": "Check the maths",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        880,
        0
      ],
      "notes": "Deterministic checks: Claude reads, code verifies.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "id": "8008d70a-650d-5985-8fc5-ab9d179b0fc1",
              "leftValue": "={{ $json.ok_to_book }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "fa0ac892-b273-5e36-91a2-e5e7980765b0",
      "name": "Safe to book?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1100,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://api.xero.com/api.xro/2.0/Invoices",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "xeroOAuth2Api",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ Invoices: [{ Type: 'ACCPAY', Contact: { Name: $json.supplier }, InvoiceNumber: $json.invoice_number, Date: $json.invoice_date, DueDate: $json.due_date, CurrencyCode: $json.currency, LineItems: [{ Description: 'Invoice ' + $json.invoice_number, Quantity: 1, UnitAmount: $json.subtotal, TaxAmount: $json.tax }], Status: 'DRAFT' }] }) }}",
        "options": {}
      },
      "id": "6b2da6b2-1bc6-5194-b766-a2a0c28da4f2",
      "name": "Create bill in accounting",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1320,
        -100
      ],
      "credentials": {
        "xeroOAuth2Api": {
          "id": "",
          "name": "Xero"
        }
      },
      "notes": "Created as a draft so a person approves payment. Swap for QuickBooks or your ERP.",
      "notesInFlow": true
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#accounts",
          "mode": "name"
        },
        "text": "=:mag: Invoice needs a look ({{ $json.reason }}): {{ $json.supplier ?? 'unknown supplier' }} {{ $json.invoice_number ?? '' }}, total {{ $json.total ?? '?' }} {{ $json.currency ?? '' }}",
        "otherOptions": {}
      },
      "id": "127dd059-6528-5f38-ac5c-1fec1cdf810a",
      "name": "Send to review queue",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.2,
      "position": [
        1320,
        100
      ],
      "credentials": {
        "slackApi": {
          "id": "",
          "name": "Slack"
        }
      }
    }
  ],
  "connections": {
    "Invoice arrives by email": {
      "main": [
        [
          {
            "node": "Read the PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the PDF": {
      "main": [
        [
          {
            "node": "Extract fields with Claude",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract fields with Claude": {
      "main": [
        [
          {
            "node": "Read the fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read the fields": {
      "main": [
        [
          {
            "node": "Check the maths",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check the maths": {
      "main": [
        [
          {
            "node": "Safe to book?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Safe to book?": {
      "main": [
        [
          {
            "node": "Create bill in accounting",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send to review queue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false,
    "adev": {
      "slug": "invoice-processing",
      "service": "document-ai",
      "needs": [
        "n8n",
        "Anthropic API key",
        "Gmail",
        "Xero (or your accounting tool)",
        "Slack"
      ],
      "summary": "Supplier invoices are read by Claude, checked by code (do the totals add up? is anything missing?) and booked as drafts. Anything doubtful goes to a person."
    }
  },
  "tags": [
    {
      "name": "adev.build"
    }
  ]
}