Version all workflows at v1.0
This commit is contained in:
348
n8n-workflows/03-paperless-ai-processing-v1.0.json
Normal file
348
n8n-workflows/03-paperless-ai-processing-v1.0.json
Normal file
@@ -0,0 +1,348 @@
|
||||
{
|
||||
"updatedAt": "2026-05-07T20:24:47.896Z",
|
||||
"createdAt": "2026-05-07T20:24:40.153Z",
|
||||
"id": "dQ1d5deV4mF0s5eP",
|
||||
"name": "Paperless AI Processing v1.0",
|
||||
"description": null,
|
||||
"active": true,
|
||||
"isArchived": false,
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"httpMethod": "POST",
|
||||
"path": "paperless/new-document",
|
||||
"options": {}
|
||||
},
|
||||
"id": "3d8bc021-e27f-4f7b-b716-a202d66551be",
|
||||
"name": "Paperless Webhook",
|
||||
"type": "n8n-nodes-base.webhook",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"webhookId": "fd1eaa3a-fbf1-4a17-94fd-bb483df602b7"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "// Extract document ID from whatever Paperless sends\nconst data = $input.first().json;\nconst body = data.body || data;\n\n// Try multiple possible field names and formats\nlet docId = body.document_id || body.id || body.doc_id || body.pk;\n\n// If it's a string like '{document_id}' (template not resolved), fall back to latest doc\nif (!docId || typeof docId === 'string' && docId.includes('{')) {\n docId = null;\n}\n\nreturn [{ json: { docId } }];"
|
||||
},
|
||||
"id": "48277e9e-a835-4e12-8608-63e905abe74d",
|
||||
"name": "Extract Document ID",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
224,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"url": "=https://paperless.paccoco.com/api/documents/{{ $json.docId ? $json.docId + '/' : '?ordering=-added&page_size=1' }}",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Token 4fb34ba35b4ab76a0715e8c56faf00a31de4fb5d"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "17ed7e6f-ccaa-43d3-9031-9f3d2bb12dec",
|
||||
"name": "Fetch Document",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
448,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const response = $input.first().json;\n\n// Handle both single doc response and list response\nconst doc = response.results ? response.results[0] : response;\n\nif (!doc || !doc.id) {\n throw new Error('No document found in response');\n}\n\n// Content is already in the metadata from Paperless OCR\nconst content = (doc.content || '').substring(0, 4000);\n\nreturn [{\n json: {\n documentId: doc.id,\n title: doc.title || 'Untitled',\n originalFilename: doc.original_file_name || '',\n correspondent: doc.correspondent_name || null,\n content: content,\n created: doc.created || new Date().toISOString()\n }\n}];"
|
||||
},
|
||||
"id": "3a026b3b-141b-48b4-a9ae-03998914f45c",
|
||||
"name": "Prepare Content",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
672,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "http://10.5.1.6:4000/v1/chat/completions",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Bearer sk-a95bd142d43d175b6476ebc862e81aa1f6ef34b1153f839698d07541d2f55308"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"model\": \"medium\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a document classification assistant. Analyze the document and return ONLY valid JSON. Do not include markdown. Do not include code fences.\\n\\nReturn this exact JSON structure:\\n{\\n \\\"summary\\\": \\\"short 1-2 sentence summary with no sensitive identifiers\\\",\\n \\\"doc_type\\\": \\\"invoice | receipt | letter | contract | manual | report | medical | tax | military | other\\\",\\n \\\"suggested_tags\\\": [\\\"tag1\\\", \\\"tag2\\\", \\\"tag3\\\"]\\n}\\n\\nIf the document is sensitive, summarize only generally and say sensitive personal identifiers were present and redacted. If the document is not sensitive, provide a normal 1-2 sentence summary. Never mention or summarize sensitive field categories such as SSNs, dates of birth, addresses, phone numbers, financial amounts, ID numbers, service numbers, or exact dates.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{ JSON.stringify('Document title: ' + ($json.title || 'Untitled') + '\\nFilename: ' + ($json.originalFilename || '') + '\\nCorrespondent: ' + ($json.correspondent || 'Unknown') + '\\n\\nContent:\\n' + ($json.content || '')).slice(1, -1) }}\"\n }\n ],\n \"max_tokens\": 350,\n \"temperature\": 0.1,\n \"response_format\": {\n \"type\": \"json_object\"\n }\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "51374897-d90e-49a2-889e-0476817dd9d8",
|
||||
"name": "AI Classify & Extract",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
880,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const response = $input.first().json;\nconst docData = $('Prepare Content').first().json;\n\nconst raw = response.choices?.[0]?.message?.content || '{}';\nlet analysis;\n\ntry {\n analysis = JSON.parse(raw);\n} catch (e) {\n analysis = {\n summary: 'Document processed, but AI response could not be parsed cleanly. Sensitive details were not included.',\n doc_type: 'other',\n suggested_tags: []\n };\n}\n\nfunction cleanText(value) {\n return String(value || '')\n .replace(/\\b\\d{3}[- ]?\\d{2}[- ]?\\d{4}\\b/g, '[REDACTED_SSN]')\n .replace(/\\b\\d{8}\\b/g, '[REDACTED_DATE]')\n .replace(/\\b\\d{1,5}\\s+[A-Za-z0-9 .'-]+\\s+(Road|RD|Street|St|Avenue|Ave|Drive|Dr|Lane|Ln|Court|Ct|Branch|Blvd|Boulevard)\\b[^,\\n]*/gi, '[REDACTED_ADDRESS]')\n .replace(/\\b\\d{5}(?:-\\d{4})?\\b/g, '[REDACTED_ZIP]');\n}\n\nconst tags = Array.isArray(analysis.suggested_tags)\n ? analysis.suggested_tags\n .map(t => cleanText(t).trim().toLowerCase())\n .filter(Boolean)\n .map(t => t.replace(/[^a-z0-9 _-]/gi, '').trim())\n .filter(Boolean)\n .slice(0, 5)\n : [];\n\nreturn [\n {\n json: {\n documentId: docData.documentId,\n title: cleanText(docData.title || 'Untitled'),\n summary: cleanText(analysis.summary || 'Document processed.'),\n doc_type: cleanText(analysis.doc_type || 'other').toLowerCase(),\n suggested_tags: tags\n }\n }\n];"
|
||||
},
|
||||
"id": "1f37f096-132b-43b4-bcb4-f5ed094bc9e5",
|
||||
"name": "Parse Analysis",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1104,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"method": "POST",
|
||||
"url": "=https://paperless.paccoco.com/api/documents/{{ $json.documentId }}/notes/",
|
||||
"sendHeaders": true,
|
||||
"headerParameters": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"value": "Token 4fb34ba35b4ab76a0715e8c56faf00a31de4fb5d"
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sendBody": true,
|
||||
"specifyBody": "json",
|
||||
"jsonBody": "={\n \"note\": \"{{ JSON.stringify('**AI Analysis**\\nType: ' + ($json.doc_type || 'other') + '\\nSummary: ' + ($json.summary || '') + '\\nSuggested tags: ' + (($json.suggested_tags || []).join(', '))).slice(1, -1) }}\"\n}",
|
||||
"options": {}
|
||||
},
|
||||
"id": "5597c8c1-436e-4373-bb54-0180be4e7bca",
|
||||
"name": "Add Note to Document",
|
||||
"type": "n8n-nodes-base.httpRequest",
|
||||
"typeVersion": 4.2,
|
||||
"position": [
|
||||
1328,
|
||||
0
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const data = $input.first().json;\n\nconst tags = Array.isArray(data.suggested_tags)\n ? data.suggested_tags.join(', ')\n : 'None';\n\nconst message = [\n data.title || 'Untitled',\n '',\n `Type: ${data.doc_type || 'other'}`,\n '',\n `Summary: ${data.summary || 'No summary returned'}`,\n '',\n `Suggested tags: ${tags || 'None'}`\n].join('\\n');\n\nreturn [{\n json: {\n title: '\ud83d\udcc4 New Document Processed',\n message,\n priority: 4\n }\n}];"
|
||||
},
|
||||
"id": "c3319471-e3f1-4cd3-aae1-c794f081b1e3",
|
||||
"name": "Format Gotify Document Message",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1328,
|
||||
208
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"message": "={{ $json.message }}",
|
||||
"additionalFields": {
|
||||
"priority": "={{ $json.priority }}",
|
||||
"title": "={{ $json.title }}"
|
||||
},
|
||||
"options": {
|
||||
"contentType": "text/plain"
|
||||
}
|
||||
},
|
||||
"id": "193f19dc-2ad6-41af-bd29-a0f5dc0ef1a0",
|
||||
"name": "Send Gotify Message",
|
||||
"type": "n8n-nodes-base.gotify",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1552,
|
||||
208
|
||||
],
|
||||
"credentials": {
|
||||
"gotifyApi": {
|
||||
"id": "ajvRjvj0QldLQYmo",
|
||||
"name": "Paperless"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"jsCode": "const data = $input.first().json;\n\nconst PAPERLESS_BASE_URL = 'https://paperless.paccoco.com';\nconst PAPERLESS_TOKEN = '4fb34ba35b4ab76a0715e8c56faf00a31de4fb5d';\n\nconst headers = {\n Authorization: `Token ${PAPERLESS_TOKEN}`,\n 'Content-Type': 'application/json',\n};\n\nfunction normalizeTagName(tag) {\n return String(tag || '')\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9 _-]/gi, '')\n .replace(/\\s+/g, ' ')\n .slice(0, 64);\n}\n\nconst suggestedTags = Array.isArray(data.suggested_tags)\n ? [...new Set(data.suggested_tags.map(normalizeTagName).filter(Boolean))].slice(0, 5)\n : [];\n\nif (!data.documentId) {\n throw new Error('No documentId found; cannot apply tags.');\n}\n\nif (suggestedTags.length === 0) {\n return [{\n json: {\n ...data,\n tag_apply_status: 'skipped_no_suggested_tags',\n applied_tag_names: [],\n applied_tag_ids: []\n }\n }];\n}\n\nasync function request(method, url, body) {\n const options = {\n method,\n url,\n headers,\n json: true,\n };\n\n if (body !== undefined) {\n options.body = body;\n }\n\n return await this.helpers.httpRequest(options);\n}\n\n// Fetch all existing tags. Handles pagination.\nconst existingTags = [];\nlet tagsUrl = `${PAPERLESS_BASE_URL}/api/tags/?page_size=100`;\n\nwhile (tagsUrl) {\n const page = await request.call(this, 'GET', tagsUrl);\n const results = Array.isArray(page.results) ? page.results : (Array.isArray(page) ? page : []);\n existingTags.push(...results);\n\n if (page.next) {\n tagsUrl = page.next.startsWith('http') ? page.next : `${PAPERLESS_BASE_URL}${page.next}`;\n } else {\n tagsUrl = null;\n }\n}\n\nconst tagsByName = new Map(\n existingTags.map(tag => [normalizeTagName(tag.name), tag])\n);\n\nconst appliedTags = [];\n\nfor (const tagName of suggestedTags) {\n let tag = tagsByName.get(tagName);\n\n if (!tag) {\n tag = await request.call(this, 'POST', `${PAPERLESS_BASE_URL}/api/tags/`, {\n name: tagName,\n });\n tagsByName.set(tagName, tag);\n }\n\n if (tag?.id) {\n appliedTags.push(tag);\n }\n}\n\n// Fetch current document so we merge tags instead of overwriting existing ones.\nconst doc = await request.call(this, 'GET', `${PAPERLESS_BASE_URL}/api/documents/${data.documentId}/`);\nconst currentTagIds = Array.isArray(doc.tags) ? doc.tags : [];\nconst appliedTagIds = appliedTags.map(tag => tag.id);\nconst mergedTagIds = [...new Set([...currentTagIds, ...appliedTagIds])];\n\nawait request.call(this, 'PATCH', `${PAPERLESS_BASE_URL}/api/documents/${data.documentId}/`, {\n tags: mergedTagIds,\n});\n\nreturn [{\n json: {\n ...data,\n tag_apply_status: 'updated',\n applied_tag_names: appliedTags.map(tag => tag.name),\n applied_tag_ids: appliedTagIds,\n final_tag_ids: mergedTagIds,\n }\n}];"
|
||||
},
|
||||
"id": "b0d5423b-dc83-4adf-98c6-d95afa3e5c1b",
|
||||
"name": "Apply Suggested Tags to Paperless",
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
1328,
|
||||
400
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Paperless Webhook": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Extract Document ID",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Extract Document ID": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Fetch Document",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Fetch Document": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Prepare Content",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Prepare Content": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "AI Classify & Extract",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"AI Classify & Extract": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Parse Analysis",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Parse Analysis": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Add Note to Document",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Format Gotify Document Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Apply Suggested Tags to Paperless",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Format Gotify Document Message": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Send Gotify Message",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"executionOrder": "v1",
|
||||
"binaryMode": "separate"
|
||||
},
|
||||
"staticData": null,
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true
|
||||
},
|
||||
"pinData": {},
|
||||
"versionId": "199d2609-e3e7-46aa-b144-df038eb10a0d",
|
||||
"activeVersionId": "199d2609-e3e7-46aa-b144-df038eb10a0d",
|
||||
"versionCounter": 11,
|
||||
"triggerCount": 1,
|
||||
"tags": [
|
||||
{
|
||||
"updatedAt": "2026-05-06T22:39:46.433Z",
|
||||
"createdAt": "2026-05-06T22:39:46.433Z",
|
||||
"id": "dZUptsMy60GbLHft",
|
||||
"name": "documents"
|
||||
},
|
||||
{
|
||||
"updatedAt": "2026-05-06T22:38:51.678Z",
|
||||
"createdAt": "2026-05-06T22:38:51.678Z",
|
||||
"id": "S9FxzVfHLsHmyzyt",
|
||||
"name": "ai"
|
||||
},
|
||||
{
|
||||
"updatedAt": "2026-05-06T22:39:46.476Z",
|
||||
"createdAt": "2026-05-06T22:39:46.476Z",
|
||||
"id": "NxHjuO4MMd5kdPR7",
|
||||
"name": "paperless"
|
||||
}
|
||||
],
|
||||
"shared": [
|
||||
{
|
||||
"updatedAt": "2026-05-07T20:24:40.153Z",
|
||||
"createdAt": "2026-05-07T20:24:40.153Z",
|
||||
"role": "workflow:owner",
|
||||
"workflowId": "dQ1d5deV4mF0s5eP",
|
||||
"projectId": "dxCRnBdX5uJizCGa",
|
||||
"project": {
|
||||
"updatedAt": "2026-05-06T01:10:37.484Z",
|
||||
"createdAt": "2026-05-06T01:08:07.721Z",
|
||||
"id": "dxCRnBdX5uJizCGa",
|
||||
"name": "Wilfred Fizzlepoof <admin@paccoco.com>",
|
||||
"type": "personal",
|
||||
"icon": null,
|
||||
"description": null,
|
||||
"creatorId": "47b2227f-2fc2-4a08-bd35-ea157b92df0d"
|
||||
}
|
||||
}
|
||||
],
|
||||
"versionMetadata": {
|
||||
"name": "Version 199d2609",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user