This workflow corresponds to n8n.io template #14210 — we link there as the canonical source.
This workflow follows the Agent → Google Sheets recipe pattern — see all workflows that pair these two integrations.
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": "XPZ1oP4v2FWiHCTh",
"name": "LINE Invoice Scanner \u2014 Auto-log to Google Sheets with OCR & Gemini AI",
"tags": [
{
"id": "ityRkKsFla2sfy7R",
"name": "Webhook",
"createdAt": "2026-03-26T10:31:18.175Z",
"updatedAt": "2026-03-26T10:31:18.175Z"
},
{
"id": "56BgGGyNcvhlmf3L",
"name": "\u8acb\u6c42\u66f8OCR",
"createdAt": "2026-03-26T10:31:18.235Z",
"updatedAt": "2026-03-26T10:31:18.235Z"
},
{
"id": "XEQR8pEbKRzbpFHH",
"name": "Gemini",
"createdAt": "2026-03-26T10:31:18.274Z",
"updatedAt": "2026-03-26T10:31:18.274Z"
},
{
"id": "Ke84WGy4KfZbEO9O",
"name": "Google Sheets",
"createdAt": "2026-03-26T10:31:18.300Z",
"updatedAt": "2026-03-26T10:31:18.300Z"
},
{
"id": "QDD9xi3AsEBhYE6p",
"name": "Basic LLM Chain",
"createdAt": "2026-03-26T10:31:18.327Z",
"updatedAt": "2026-03-26T10:31:18.327Z"
},
{
"id": "xq8850GZ4YeKwowz",
"name": "LINE",
"createdAt": "2026-03-26T10:31:18.357Z",
"updatedAt": "2026-03-26T10:31:18.357Z"
}
],
"nodes": [
{
"id": "4884128d-48e0-4d8d-bd1d-0509757d36b8",
"name": "Google Gemini Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
"position": [
2240,
896
],
"parameters": {
"options": {}
},
"typeVersion": 1
},
{
"id": "332f9bf7-84c7-4357-a310-f23698b74598",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
-32
],
"parameters": {
"width": 768,
"height": 832,
"content": "### How it works\n\nSend a photo of any invoice to the LINE bot and the rest is handled automatically. The workflow picks up the image via LINE Messaging API, pushes it through OCR.space to pull out the raw text, then hands that text off to a Gemini AI agent. The agent identifies the key fields \u2014 invoice number, issue date, due date, vendor name, subtotal, tax, and total \u2014 and writes them as a new row in your Google Sheets ledger. A confirmation is sent back to LINE when everything is logged. If the image isn't an invoice, the bot replies with a friendly error message instead.\n\n### Setup steps\n\n1. Create a Messaging API channel in LINE Developers and grab a long-lived channel access token\n2. Get a free OCR.space API key at ocr.space/ocrapi/freekey (25,000 requests/month free)\n3. Get a free Gemini API key at aistudio.google.com (1,500 requests/day free)\n4. Connect your Google account via Google Sheets OAuth2 in n8n Credentials\n5. Open the Variables node \u2014 it's the only place you need to enter anything\n6. Paste your LINE token, OCR key, and Spreadsheet ID there\n7. Set your Webhook URL in LINE Developers to match your n8n instance URL\n8. Activate the workflow and send a test invoice photo to your bot\n\n### Customization\n\nSwap `SHEET_NAME` in the Variables node to file invoices by month or project. Edit the Gemini prompt to capture extra fields like bank details or itemized lines. Replace OCR.space with Google Cloud Vision if you need better accuracy on complex or handwritten layouts."
},
"typeVersion": 1
},
{
"id": "c0611d9c-86a6-4bee-bf92-66c59aaa4493",
"name": "Receive LINE Webhook",
"type": "n8n-nodes-base.webhook",
"notes": "Set this Webhook URL in LINE Developers > Messaging API > Webhook URL.\nRemember to activate the workflow before testing.",
"position": [
832,
864
],
"parameters": {
"path": "line-invoice",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2
},
{
"id": "57aa751e-4673-45eb-85df-4aefc39eaee1",
"name": "Return 200 OK to LINE",
"type": "n8n-nodes-base.respondToWebhook",
"notes": "LINE requires a 200 OK response immediately after receiving a webhook, otherwise it will retry the delivery. This node handles that.",
"position": [
1056,
960
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "{\"status\":\"ok\"}"
},
"typeVersion": 1
},
{
"id": "0aaf96c2-2c3f-40af-a3c0-1c0a0e15fd00",
"name": "Check Image Message",
"type": "n8n-nodes-base.if",
"notes": "Only image messages continue through this workflow. Text and other message types are ignored here.",
"position": [
1280,
768
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-image",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Receive LINE Webhook').item.json.body.events[0].message.type }}",
"rightValue": "image"
}
]
}
},
"typeVersion": 2
},
{
"id": "d150d722-c92f-4e49-9f73-5be948a35ba4",
"name": "Variables (API Key Management)",
"type": "n8n-nodes-base.set",
"notes": "This is the only node you need to configure.\n\nLINE_CHANNEL_ACCESS_TOKEN \u2014 find this in LINE Developers > Messaging API > Channel access token (long-lived)\nOCR_API_KEY \u2014 from ocr.space/ocrapi/freekey\nSPREADSHEET_ID \u2014 from your Google Sheets URL\nSHEET_NAME \u2014 must match the tab name in your spreadsheet",
"position": [
1056,
768
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "var-001",
"name": "LINE_CHANNEL_ACCESS_TOKEN",
"type": "string",
"value": "User Key"
},
{
"id": "var-002",
"name": "LINE_MESSAGE_ID",
"type": "string",
"value": "=User Key"
},
{
"id": "var-003",
"name": "SPREADSHEET_ID",
"type": "string",
"value": "User Key"
},
{
"id": "var-004",
"name": "SHEET_NAME",
"type": "string",
"value": "2026\u5e7403\u6708\u5206"
},
{
"id": "28a2dc7a-5ca7-42ba-8093-91137e29b7d9",
"name": "OCR_SPACE_API_KEY",
"type": "string",
"value": "User Key"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "ca1b9626-bb9b-411a-8703-7eb91145c44f",
"name": "Fetch Image from LINE",
"type": "n8n-nodes-base.httpRequest",
"notes": "Downloads the image binary from LINE's content API.\nNote: the host is api-data.line.me, not api.line.me \u2014 do not mix these up.\nResponse format must be set to File.",
"position": [
1504,
672
],
"parameters": {
"url": "=https://api-data.line.me/v2/bot/message/{{$json.LINE_MESSAGE_ID}}/content",
"options": {
"response": {
"response": {
"responseFormat": "file"
}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{$json.LINE_CHANNEL_ACCESS_TOKEN}}"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "d690ddd9-c613-4194-8c26-af52d8392f81",
"name": "Convert Image to Base64",
"type": "n8n-nodes-base.code",
"position": [
1728,
672
],
"parameters": {
"jsCode": "const binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst base64 = binaryData.toString('base64');\n\nreturn [{\n json: {\n data: base64,\n mimeType: 'image/jpeg'\n }\n}];"
},
"typeVersion": 2
},
{
"id": "9d8594dc-ac9d-4574-830c-1aee3bd68448",
"name": "OCR - Extract Text from Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
1952,
672
],
"parameters": {
"url": "=https://api.ocr.space/parse/image",
"method": "POST",
"options": {
"response": {
"response": {}
}
},
"sendBody": true,
"contentType": "multipart-form-data",
"sendHeaders": true,
"bodyParameters": {
"parameters": [
{
"name": "base64Image",
"value": "=data:image/jpeg;base64,{{ $json.data }}"
},
{
"name": "language",
"value": "jpn"
},
{
"name": "isTable",
"value": "true"
},
{
"name": "OCREngine",
"value": "2"
}
]
},
"headerParameters": {
"parameters": [
{
"name": "apikey",
"value": "={{ $('Variables (API Key Management)').item.json.OCR_SPACE_API_KEY }}"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "d26bc118-71ef-42fe-825f-41c31383320e",
"name": "AI Agent - Parse Invoice with Gemini",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
2176,
672
],
"parameters": {
"text": "=\u4ee5\u4e0b\u306f\u8acb\u6c42\u66f8\u3092OCR\u3067\u8aad\u307f\u53d6\u3063\u305f\u30c6\u30ad\u30b9\u30c8\u3067\u3059\u3002\n\u3053\u306e\u30c6\u30ad\u30b9\u30c8\u304b\u3089\u8acb\u6c42\u66f8\u30c7\u30fc\u30bf\u3092\u62bd\u51fa\u3057\u3066JSON\u5f62\u5f0f\u306e\u307f\u3067\u8fd4\u3057\u3066\u304f\u3060\u3055\u3044\u3002\n\n\u3010OCR\u30c6\u30ad\u30b9\u30c8\u3011\n{{ $('OCR - Extract Text from Image').item.json.ParsedResults[0].ParsedText }}\n\n\u3010\u91cd\u8981\u30eb\u30fc\u30eb\u3011\n\u30fbclient_name: \u8acb\u6c42\u66f8\u306e\u53f3\u4e0a\u306b\u66f8\u3044\u3066\u3042\u308b\u81ea\u5206\u306e\u4f1a\u793e\u540d\n\u30fb\u91d1\u984d: \u6570\u5b57\u306e\u307f\uff08\u30ab\u30f3\u30de\u30fb\u5186\u30de\u30fc\u30af\u30fb\u300c\u5186\u300d\u4e0d\u8981\uff09\n\u30fb\u65e5\u4ed8: YYYY-MM-DD\u5f62\u5f0f\n\u30fb\u8acb\u6c42\u66f8\u756a\u53f7: \u306a\u3044\u5834\u5408\u306f\u7a7a\u6587\u5b57\n\n\u3010\u51fa\u529b\u5f62\u5f0f\u3011\n{\n \"error\": false,\n \"invoice_number\": \"\u8acb\u6c42\u66f8\u756a\u53f7\",\n \"invoice_date\": \"YYYY-MM-DD\",\n \"due_date\": \"YYYY-MM-DD\",\n \"client_name\": \"\u53f3\u4e0a\u306b\u66f8\u3044\u3066\u3042\u308b\u81ea\u5206\u306e\u4f1a\u793e\u540d\",\n \"subtotal\": 0,\n \"tax_amount\": 0,\n \"total_amount\": 0,\n \"currency\": \"JPY\",\n \"notes\": \"\u5099\u8003\u6b04\u306e\u5185\u5bb9\"\n}\n\n\u753b\u50cf\u304c\u8acb\u6c42\u66f8\u3067\u306a\u3044\u5834\u5408\u306e\u307f:\n{ \"error\": true }",
"options": {},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "7131ca36-708d-46b4-aa66-1eed8d37d9fa",
"name": "Parse & Format JSON",
"type": "n8n-nodes-base.code",
"notes": "Strips Markdown code fences from the Gemini response, converts numeric fields to the correct type, and records the processed timestamp in Japan Standard Time (JST).",
"position": [
2528,
672
],
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (const item of items) {\n try {\n const raw = item.json.text || item.json.output || '';\n\n const cleaned = raw\n .replace(/^```json\\s*/i, '')\n .replace(/^```\\s*/i, '')\n .replace(/\\s*```$/i, '')\n .trim();\n\n const parsed = JSON.parse(cleaned);\n\n const toNum = (val) =>\n Number(String(val || '0').replace(/[^0-9.]/g, '')) || 0;\n\n results.push({\n json: {\n error: false, // \u2190 \u3053\u3053\u306b\u8ffd\u52a0\uff01\n invoice_number: parsed.invoice_number || '',\n invoice_date: parsed.invoice_date || '',\n due_date: parsed.due_date || '',\n client_name: parsed.client_name || '',\n subtotal: toNum(parsed.subtotal),\n tax_amount: toNum(parsed.tax_amount),\n total_amount: toNum(parsed.total_amount),\n currency: parsed.currency || 'JPY',\n notes: parsed.notes || '',\n processed_at: new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })\n }\n });\n\n } catch (e) {\n results.push({\n json: {\n error: false,\n error_message: `JSON\u30d1\u30fc\u30b9\u5931\u6557: ${e.message}`,\n raw_response: item.json.text || '\u30ec\u30b9\u30dd\u30f3\u30b9\u306a\u3057',\n processed_at: new Date().toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })\n }\n });\n }\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "5c0906e4-e89a-4f24-88dd-685c5979b9b5",
"name": "Error Check",
"type": "n8n-nodes-base.if",
"position": [
2752,
672
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "35f8a6cc-80a9-4b5f-ae1a-21499bcf9f03",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.error }}",
"rightValue": false
}
]
}
},
"typeVersion": 2.3
},
{
"id": "e8db1063-2a25-430b-ae0c-719c099305d1",
"name": "Append to Google Sheets",
"type": "n8n-nodes-base.googleSheets",
"notes": "Add these headers to row 1 of your sheet before running:\nA1: Invoice No | B1: Issue Date | C1: Due Date | D1: Vendor | E1: Subtotal | F1: Tax | G1: Total | H1: Currency | I1: Notes | J1: Processed At\n\nConnect your Google account via n8n Credentials > Google Sheets OAuth2 API.",
"position": [
2976,
576
],
"parameters": {
"columns": {
"value": {
"\u5c0f\u8a08": "={{ $json.subtotal }}",
"\u901a\u8ca8": "={{ $json.currency }}",
"\u8acb\u6c42\u65e5": "={{ $json.invoice_date }}",
"\u51e6\u7406\u65e5\u6642": "={{ $json.processed_at }}",
"\u53d6\u5f15\u5148\u540d": "={{ $json.client_name }}",
"\u5408\u8a08\u91d1\u984d": "={{ $json.total_amount }}",
"\u652f\u6255\u671f\u9650": "={{ $json.due_date }}",
"\u6d88\u8cbb\u7a0e\u984d": "={{ $json.tax_amount }}",
"\u8acb\u6c42\u66f8\u756a\u53f7": "={{ $json.invoice_number }}"
},
"schema": [
{
"id": "\u8acb\u6c42\u66f8\u756a\u53f7",
"type": "string",
"display": true,
"required": false,
"displayName": "\u8acb\u6c42\u66f8\u756a\u53f7",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u8acb\u6c42\u65e5",
"type": "string",
"display": true,
"required": false,
"displayName": "\u8acb\u6c42\u65e5",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u652f\u6255\u671f\u9650",
"type": "string",
"display": true,
"required": false,
"displayName": "\u652f\u6255\u671f\u9650",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u53d6\u5f15\u5148\u540d",
"type": "string",
"display": true,
"required": false,
"displayName": "\u53d6\u5f15\u5148\u540d",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u5c0f\u8a08",
"type": "string",
"display": true,
"required": false,
"displayName": "\u5c0f\u8a08",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u6d88\u8cbb\u7a0e\u984d",
"type": "string",
"display": true,
"required": false,
"displayName": "\u6d88\u8cbb\u7a0e\u984d",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u5408\u8a08\u91d1\u984d",
"type": "string",
"display": true,
"required": false,
"displayName": "\u5408\u8a08\u91d1\u984d",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u901a\u8ca8",
"type": "string",
"display": true,
"required": false,
"displayName": "\u901a\u8ca8",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u5099\u8003",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "\u5099\u8003",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "\u51e6\u7406\u65e5\u6642",
"type": "string",
"display": true,
"required": false,
"displayName": "\u51e6\u7406\u65e5\u6642",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Xt4sqICf90Ka-Q9BcARB2Kk85Ycj638Hz4PT-g4S7JE/edit#gid=0",
"cachedResultName": "\u30b7\u30fc\u30c81"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "=https://docs.google.com/spreadsheets/d/1Xt4sqICf90Ka-Q9BcARB2Kk85Ycj638Hz4PT-g4S7JE/edit?gid=0#gid=0"
}
},
"typeVersion": 4.4
},
{
"id": "36693043-1824-4036-a0e1-b60090ce52df",
"name": "LINE Reply - Success",
"type": "n8n-nodes-base.httpRequest",
"position": [
3200,
576
],
"parameters": {
"url": "https://api.line.me/v2/bot/message/reply",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replyToken\": \"{{ $('Receive LINE Webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\": \"\u2705 \u30b9\u30d7\u30ec\u30c3\u30c9\u30b7\u30fc\u30c8\u306b\u767b\u9332\u3057\u307e\u3057\u305f\uff01\\n\\n\u53d6\u5f15\u5148: {{ $('Parse & Format JSON').item.json.client_name }}\\n\u8acb\u6c42\u66f8\u756a\u53f7: {{ $('Parse & Format JSON').item.json.invoice_number }}\\n\u8acb\u6c42\u65e5: {{ $('Parse & Format JSON').item.json.invoice_date }}\\n\u5408\u8a08\u91d1\u984d: \u00a5{{ $('Parse & Format JSON').item.json.total_amount }}\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('Variables (API Key Management)').item.json.LINE_CHANNEL_ACCESS_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "843acd2b-4e64-425b-8da8-244653ab8ee4",
"name": "LINE Reply - Error",
"type": "n8n-nodes-base.httpRequest",
"position": [
2976,
768
],
"parameters": {
"url": "https://api.line.me/v2/bot/message/reply",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replyToken\": \"{{ $('Receive LINE Webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\": \"\u8acb\u6c42\u66f8\u304c\u8aad\u307f\u53d6\u308c\u307e\u305b\u3093\u3093\u3067\u3057\u305f\u3002\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('Variables (API Key Management)').item.json.LINE_CHANNEL_ACCESS_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "d53f474d-f3bc-4740-bace-8ae5233ef30b",
"name": "LINE Reply - Not an Image",
"type": "n8n-nodes-base.httpRequest",
"position": [
1504,
864
],
"parameters": {
"url": "https://api.line.me/v2/bot/message/reply",
"method": "POST",
"options": {},
"jsonBody": "={\n \"replyToken\": \"{{ $('Receive LINE Webhook').item.json.body.events[0].replyToken }}\",\n \"messages\": [\n {\n \"type\": \"text\",\n \"text\": \"\u8acb\u6c42\u66f8\u30c7\u30fc\u30bf\u3092\u9001\u3063\u3066\u304f\u3060\u3055\u3044\"\n }\n ]\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "=Bearer {{ $('Variables (API Key Management)').item.json.LINE_CHANNEL_ACCESS_TOKEN }}"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.4
},
{
"id": "c16b8ff5-a39a-4813-b47e-6fe404d3c2cb",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
432
],
"parameters": {
"color": 7,
"width": 288,
"height": 208,
"content": "## Image Intake\nReceives the invoice image from LINE via Webhook, filters for image messages only, and downloads the file for processing."
},
"typeVersion": 1
},
{
"id": "7b856104-c72c-433a-9f91-0b826824465c",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1792,
448
],
"parameters": {
"color": 7,
"content": "## Text Extraction\nConverts the image to Base64 and sends it to OCR.space, which returns the raw text content of the invoice."
},
"typeVersion": 1
},
{
"id": "c0ee045b-f368-4c81-a772-ceb84fa0f0a2",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2160,
416
],
"parameters": {
"color": 7,
"content": "## AI Parsing\nGemini AI reads the raw OCR text and structures it into labelled invoice fields, returned as clean JSON."
},
"typeVersion": 1
},
{
"id": "de76afc5-39da-4b36-a286-002971cc4ad6",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
2896,
352
],
"parameters": {
"color": 7,
"content": "## Log & Notify\nAppends the parsed invoice data to Google Sheets and sends a success confirmation back to the user on LINE."
},
"typeVersion": 1
},
{
"id": "aaf9abf1-ece5-42ff-a2fd-d93afc1a8cb8",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2576,
384
],
"parameters": {
"color": 7,
"width": 288,
"height": 208,
"content": "## Error Handling\nChecks whether the AI successfully parsed the invoice and routes to the appropriate success or error reply."
},
"typeVersion": 1
},
{
"id": "83ee455c-434a-4cd6-97f1-f694389f3cc1",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
3152,
352
],
"parameters": {
"color": 7,
"width": 288,
"height": 208,
"content": "## LINE Reply\nSends a success message or a clear error message back to the user depending on the parsing result."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "c56217c3-5a5f-407e-bb5c-b958fd42f2c7",
"connections": {
"Error Check": {
"main": [
[
{
"node": "Append to Google Sheets",
"type": "main",
"index": 0
}
],
[
{
"node": "LINE Reply - Error",
"type": "main",
"index": 0
}
]
]
},
"Check Image Message": {
"main": [
[
{
"node": "Fetch Image from LINE",
"type": "main",
"index": 0
}
],
[
{
"node": "LINE Reply - Not an Image",
"type": "main",
"index": 0
}
]
]
},
"Parse & Format JSON": {
"main": [
[
{
"node": "Error Check",
"type": "main",
"index": 0
}
]
]
},
"Receive LINE Webhook": {
"main": [
[
{
"node": "Return 200 OK to LINE",
"type": "main",
"index": 0
},
{
"node": "Variables (API Key Management)",
"type": "main",
"index": 0
}
]
]
},
"Fetch Image from LINE": {
"main": [
[
{
"node": "Convert Image to Base64",
"type": "main",
"index": 0
}
]
]
},
"Append to Google Sheets": {
"main": [
[
{
"node": "LINE Reply - Success",
"type": "main",
"index": 0
}
]
]
},
"Convert Image to Base64": {
"main": [
[
{
"node": "OCR - Extract Text from Image",
"type": "main",
"index": 0
}
]
]
},
"Google Gemini Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent - Parse Invoice with Gemini",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"OCR - Extract Text from Image": {
"main": [
[
{
"node": "AI Agent - Parse Invoice with Gemini",
"type": "main",
"index": 0
}
]
]
},
"Variables (API Key Management)": {
"main": [
[
{
"node": "Check Image Message",
"type": "main",
"index": 0
}
]
]
},
"AI Agent - Parse Invoice with Gemini": {
"main": [
[
{
"node": "Parse & Format JSON",
"type": "main",
"index": 0
}
]
]
}
},
"description": "## Who is this for\n\nFreelancers, small business owners, and accounting teams in Japan who receive invoices and want to eliminate manual data entry into spreadsheets.\n\n## What this workflow does\n\nSend an invoice photo to your LINE bot and the rest is automatic. The workflow fetches the image via LINE Messaging API, runs it through OCR.space to extract raw text, then passes that text to a Gemini AI Agent to parse the key fields \u2014 invoice number, date, due date, vendor name, subtotal, tax, and total. The result is appended as a new row in your Google Sheets ledger and a confirmation is sent back to LINE. If the image is not an invoice, a friendly error message is returned instead.\n\n## How to set up\n\n1. Create a LINE Messaging API channel and get your Channel Access Token\n2. Get a free OCR.space API key at ocr.space/ocrapi/freekey\n3. Get a free Gemini API key at aistudio.google.com\n4. Set up Google Sheets OAuth2 credentials in n8n\n5. Enter your LINE token, OCR key, and Spreadsheet ID in the Variables node\n6. Set the Webhook URL in LINE Developers to your n8n instance URL\n7. Activate the workflow and send an invoice photo to your LINE bot\n\n## Requirements\n\n- LINE Messaging API channel and channel access token\n- OCR.space API key (free tier: 25,000 requests/month)\n- Google Gemini API key (free tier: 1,500 requests/day)\n- Google Sheets with OAuth2 credentials configured in n8n\n\n## How to customize\n\nChange `SHEET_NAME` in the Variables node to file invoices by month. Edit the Gemini prompt to capture extra fields like bank details or line items. Swap OCR.space for Google Cloud Vision API if you need better accuracy on complex layouts."
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Freelancers, small business owners, and accounting teams in Japan who receive invoices and want to eliminate manual data entry into spreadsheets.
Source: https://n8n.io/workflows/14210/ — 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.
⏺ 🚀 How it works
LineOA. Uses httpRequest, agent, lmChatGoogleGemini, outputParserStructured. Webhook trigger; 69 nodes.
Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.
Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.