Skip to content

In n8n, copy this file and paste it onto the canvas, or use Import from File.

{
  "name": "07 - Invoice and Receipt Extractor",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "receipt",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "m07-webhook",
      "name": "Receipt API",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        100
      ],
      "webhookId": "course-07-receipt"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "b1",
              "leftValue": "={{ Object.values($binary ?? {}).some(f => f.mimeType === 'application/pdf') }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m07-haspdf",
      "name": "Has PDF?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        220,
        100
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { error: 'upload a PDF as multipart form-data, e.g. curl -F file=@receipt.pdf' } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "m07-400",
      "name": "Respond No PDF",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        440,
        260
      ]
    },
    {
      "parameters": {
        "operation": "pdf",
        "binaryPropertyName": "={{ Object.keys($binary).find(k => $binary[k].mimeType === 'application/pdf') }}",
        "options": {
          "joinPages": true,
          "maxPages": 5
        }
      },
      "id": "m07-extract",
      "name": "Extract PDF Text",
      "type": "n8n-nodes-base.extractFromFile",
      "typeVersion": 1.1,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "text": "={{ $json.text.slice(0, 8000) }}",
        "schemaType": "fromJson",
        "jsonSchemaExample": "{\n  \"vendor\": \"Brewlab Coffee Co.\",\n  \"invoice_number\": \"INV-1001\",\n  \"date\": \"2026-01-31\",\n  \"currency\": \"USD\",\n  \"subtotal\": 20.5,\n  \"tax\": 1.7,\n  \"total\": 22.2,\n  \"category\": \"meals\",\n  \"line_items\": [\n    { \"description\": \"Latte\", \"quantity\": 2, \"amount\": 11 }\n  ]\n}",
        "options": {
          "systemPromptTemplate": "You extract data from receipts and invoices. Return dates as YYYY-MM-DD and amounts as plain numbers without currency symbols. currency is a 3-letter ISO code. category is one of: meals, travel, software, office, utilities, other. If a value is missing in the document, use null. Never invent values."
        }
      },
      "id": "m07-ai",
      "name": "Extract Receipt Data",
      "type": "@n8n/n8n-nodes-langchain.informationExtractor",
      "typeVersion": 1.2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini",
          "cachedResultName": "gpt-5-mini"
        },
        "options": {}
      },
      "id": "m07-model",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        660,
        220
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "vendor",
              "value": "={{ $json.output.vendor ?? '' }}",
              "type": "string"
            },
            {
              "id": "c2",
              "name": "invoice_number",
              "value": "={{ $json.output.invoice_number ?? '' }}",
              "type": "string"
            },
            {
              "id": "c3",
              "name": "date",
              "value": "={{ $json.output.date ?? '' }}",
              "type": "string"
            },
            {
              "id": "c4",
              "name": "currency",
              "value": "={{ ($json.output.currency ?? '').toUpperCase() }}",
              "type": "string"
            },
            {
              "id": "c5",
              "name": "subtotal",
              "value": "={{ Number($json.output.subtotal ?? 0) }}",
              "type": "number"
            },
            {
              "id": "c6",
              "name": "tax",
              "value": "={{ Number($json.output.tax ?? 0) }}",
              "type": "number"
            },
            {
              "id": "c7",
              "name": "total",
              "value": "={{ Number($json.output.total ?? 0) }}",
              "type": "number"
            },
            {
              "id": "c8",
              "name": "category",
              "value": "={{ $json.output.category ?? 'other' }}",
              "type": "string"
            },
            {
              "id": "c9",
              "name": "items_count",
              "value": "={{ ($json.output.line_items ?? []).length }}",
              "type": "number"
            },
            {
              "id": "c10",
              "name": "line_items",
              "value": "={{ $json.output.line_items ?? [] }}",
              "type": "array"
            },
            {
              "id": "c11",
              "name": "needs_review",
              "value": "={{ !$json.output.total || !$json.output.date || !$json.output.vendor }}",
              "type": "boolean"
            },
            {
              "id": "c12",
              "name": "processed_at",
              "value": "={{ $now.toISO() }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "m07-clean",
      "name": "Clean Receipt",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "d1",
              "leftValue": "={{ $('Receipt API').item.json.query.dry_run === 'true' }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "m07-dry",
      "name": "Dry Run?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1100,
        0
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/YOUR_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "Expenses"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "date": "={{ $json.date }}",
            "vendor": "={{ $json.vendor }}",
            "invoice_number": "={{ $json.invoice_number }}",
            "category": "={{ $json.category }}",
            "currency": "={{ $json.currency }}",
            "subtotal": "={{ $json.subtotal }}",
            "tax": "={{ $json.tax }}",
            "total": "={{ $json.total }}",
            "needs_review": "={{ $json.needs_review }}",
            "processed_at": "={{ $json.processed_at }}"
          },
          "matchingColumns": [],
          "schema": [
            {
              "id": "date",
              "displayName": "date",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "vendor",
              "displayName": "vendor",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "invoice_number",
              "displayName": "invoice_number",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "category",
              "displayName": "category",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "currency",
              "displayName": "currency",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "subtotal",
              "displayName": "subtotal",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "tax",
              "displayName": "tax",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "total",
              "displayName": "total",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "needs_review",
              "displayName": "needs_review",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "processed_at",
              "displayName": "processed_at",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            }
          ]
        },
        "options": {}
      },
      "id": "m07-sheets",
      "name": "Save to Expenses",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1320,
        100
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { saved: $(\"Receipt API\").item.json.query.dry_run !== \"true\", ...$(\"Clean Receipt\").item.json } }}",
        "options": {}
      },
      "id": "m07-respond",
      "name": "Respond",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        1540,
        0
      ]
    }
  ],
  "connections": {
    "Receipt API": {
      "main": [
        [
          {
            "node": "Has PDF?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has PDF?": {
      "main": [
        [
          {
            "node": "Extract PDF Text",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond No PDF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract PDF Text": {
      "main": [
        [
          {
            "node": "Extract Receipt Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Extract Receipt Data",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Extract Receipt Data": {
      "main": [
        [
          {
            "node": "Clean Receipt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Clean Receipt": {
      "main": [
        [
          {
            "node": "Dry Run?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dry Run?": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Save to Expenses",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save to Expenses": {
      "main": [
        [
          {
            "node": "Respond",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  }
}