This workflow follows the Google Drive → 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 →
{
"name": "upload_answer",
"nodes": [
{
"parameters": {
"method": "POST",
"url": "https://api.mistral.ai/v1/files",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "mistralCloudApi",
"sendBody": true,
"contentType": "multipart-form-data",
"bodyParameters": {
"parameters": [
{
"name": "purpose",
"value": "ocr"
},
{
"parameterType": "formBinaryData",
"name": "file",
"inputDataFieldName": "data"
}
]
},
"options": {}
},
"id": "9578afd6-7172-4a51-8517-a91f2883c55b",
"name": "Mistral Upload",
"type": "n8n-nodes-base.httpRequest",
"position": [
1504,
400
],
"typeVersion": 4.2,
"credentials": {
"mistralCloudApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"url": "=https://api.mistral.ai/v1/files/{{ $json.id }}/url",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "mistralCloudApi",
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "expiry",
"value": "24"
}
]
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "application/json"
}
]
},
"options": {}
},
"id": "82fce7a1-044d-4690-bb5a-bedd7b4c6760",
"name": "Mistral Signed URL",
"type": "n8n-nodes-base.httpRequest",
"position": [
1712,
400
],
"typeVersion": 4.2,
"credentials": {
"mistralCloudApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "https://api.mistral.ai/v1/ocr",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "mistralCloudApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"model\": \"mistral-ocr-latest\",\n \"document\": {\n \"type\": \"document_url\",\n \"document_url\": \"{{ $json.url }}\"\n },\n \"include_image_base64\": true\n}",
"options": {}
},
"id": "b1de2911-d682-493a-bb54-cca31fabbeec",
"name": "Mistral DOC OCR",
"type": "n8n-nodes-base.httpRequest",
"position": [
1904,
400
],
"typeVersion": 4.2,
"credentials": {
"mistralCloudApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5332d2cb-0469-4579-b112-aaa7470acc57",
"name": "markdown",
"value": "={{ ($json.pages || []).map(page => page.markdown || '').join('\\n\\n').trim() }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2112,
400
],
"id": "9ac3a56b-3794-4b6b-a8e9-ace1bb6b2848",
"name": "Edit Fields"
},
{
"parameters": {
"modelId": {
"__rl": true,
"value": "gpt-4.1-mini",
"mode": "list",
"cachedResultName": "GPT-4.1-MINI"
},
"responses": {
"values": [
{
"content": "=B\u1ea1n l\u00e0 AI tr\u00edch xu\u1ea5t b\u00e0i l\u00e0m h\u1ecdc sinh t\u1eeb OCR markdown.\n\nM\u1ee5c ti\u00eau:\nChuy\u1ec3n to\u00e0n b\u1ed9 n\u1ed9i dung b\u00e0i l\u00e0m c\u1ee7a h\u1ecdc sinh th\u00e0nh JSON CHUAN de luu database.\n\nQuy t\u1eafc b\u1eaft bu\u1ed9c:\n1. Tr\u1ea3 v\u1ec1 DUY NH\u1ea4T m\u1ed9t JSON array h\u1ee3p l\u1ec7, kh\u00f4ng c\u00f3 m\u00f4 t\u1ea3, kh\u00f4ng c\u00f3 markdown code block.\n2. M\u1ed7i ph\u1ea7n t\u1eed l\u00e0 1 c\u00e2u tr\u1ea3 l\u1eddi v\u1edbi format:\n {\n \"question_no\": string,\n \"answer_text\": string,\n \"detected_score\": number|null,\n \"confidence\": number\n }\n3. question_no: l\u1ea5y s\u1ed1/th\u1ee9 t\u1ef1 c\u00e2u (v\u00ed d\u1ee5: \"1\", \"2a\", \"III\"). N\u1ebfu kh\u00f4ng x\u00e1c \u0111\u1ecbnh \u0111\u01b0\u1ee3c th\u00ec d\u00f9ng \"unknown\".\n4. answer_text: gi\u1eef nguy\u00ean n\u1ed9i dung tr\u1ea3 l\u1eddi c\u1ee7a h\u1ecdc sinh, ch\u1ec9 chu\u1ea9n h\u00f3a kho\u1ea3ng tr\u1eafng d\u01b0 v\u00e0 xu\u1ed1ng d\u00f2ng l\u1eb7p.\n5. detected_score: n\u1ebfu th\u1ea5y \u0111i\u1ec3m gi\u00e1o vi\u00ean ghi tr\u1ef1c ti\u1ebfp trong b\u00e0i (v\u00ed d\u1ee5 1.5/2, 8 \u0111i\u1ec3m) th\u00ec tr\u00edch s\u1ed1; n\u1ebfu kh\u00f4ng c\u00f3 th\u00ec \u0111\u1ec3 null.\n6. confidence: s\u1ed1 th\u1ef1c t\u1eeb 0 \u0111\u1ebfn 1 theo \u0111\u1ed9 ch\u1eafc ch\u1eafn khi t\u00e1ch c\u00e2u tr\u1ea3 l\u1eddi.\n7. Kh\u00f4ng t\u1ef1 ch\u1ea5m \u0111i\u1ec3m, kh\u00f4ng th\u00eam rubric, kh\u00f4ng th\u00eam nh\u1eadn x\u00e9t.\n8. N\u1ebfu b\u00e0i l\u00e0m kh\u00f4ng \u0111\u1ecdc \u0111\u01b0\u1ee3c n\u1ed9i dung th\u00ec tr\u1ea3 v\u1ec1 []\n\nInput OCR markdown:\n{{ $json.markdown }}\n\nOutput v\u00ed d\u1ee5:\n[\n {\n \"question_no\": \"1\",\n \"answer_text\": \"...\",\n \"detected_score\": null,\n \"confidence\": 0.92\n }\n]"
}
]
},
"builtInTools": {},
"options": {}
},
"type": "@n8n/n8n-nodes-langchain.openAi",
"typeVersion": 2.1,
"position": [
2320,
400
],
"id": "0d440b67-94f3-4873-9429-d192bed7c212",
"name": "Message a model",
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "bfa03bec-ae48-41f8-a8f8-bffa8271cd76",
"name": "text",
"value": "={{ $json.output[0].content[0].text }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2672,
400
],
"id": "93ff5052-d75b-4823-a97c-206e28f3b083",
"name": "Edit Fields1"
},
{
"parameters": {
"httpMethod": "POST",
"path": "upload-answer-api",
"options": {}
},
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
464,
640
],
"id": "a2646983-76f8-4ffa-bf5c-09ad886e1087",
"name": "On API submission",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "const item = $input.first();\nconst binary = item.binary || {};\n\nif (!binary.submission_file && binary.file) {\n binary.submission_file = binary.file;\n}\n\nconst uploadFile = binary.submission_file;\nif (!uploadFile) {\n throw new Error('Thieu file. Gui multipart/form-data voi field file la submission_file (hoac file).');\n}\n\nconst filename = String(uploadFile.fileName || uploadFile.filename || '').toLowerCase();\nconst mimeType = String(uploadFile.mimeType || uploadFile.mimetype || '').toLowerCase();\nconst isPdf = filename.endsWith('.pdf') || mimeType === 'application/pdf';\n\nif (!isPdf) {\n throw new Error('Chi ho tro file PDF cho workflow upload_answer');\n}\n\nreturn [{ json: item.json, binary }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
704,
640
],
"id": "7bbabfe5-470e-473c-ab7c-7b11be36c026",
"name": "Validate PDF file"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "5495157c-8887-4a33-8533-d9218ed6435e",
"name": "name",
"value": "={{ $json.body.student_name }}",
"type": "string"
},
{
"id": "341ea077-c943-4b56-bff7-a14505e0c39d",
"name": "subject_code",
"value": "={{ $json.body.subject_code }}",
"type": "string"
},
{
"id": "362d9c99-49a4-4a52-8bc9-299677d969e3",
"name": "exam_id",
"value": "={{ $json.body.exam_id }}",
"type": "string"
},
{
"id": "6220ac7a-9e58-4f98-83b6-186141bebd0c",
"name": "File b\u00e0i l\u00e0m",
"value": "={{ $('Validate PDF file').item.binary?.submission_file?.fileName || $('Validate PDF file').item.binary?.submission_file?.filename || '' }}",
"type": "string"
},
{
"id": "db64ea34-b713-46ac-93c9-7616049ee8c0",
"name": "submittedAt",
"value": "={{ $now.toISO() }}",
"type": "string"
},
{
"id": "7cd24314-2d02-4f5c-b0ec-07a8accbf876",
"name": "M\u00e3 sinh vi\u00ean",
"value": "={{ $json.body.student_code }}",
"type": "string"
},
{
"id": "9f674ae0-1f15-4f6d-9f01-0f68c0ce69ef",
"name": "notes",
"value": "={{ $json.body.notes }}",
"type": "string"
},
{
"id": "33e4af66-1381-44fd-a2e0-8e50c4dbad9c",
"name": "class_code",
"value": "={{ $json.body.class_code }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
688,
1024
],
"id": "817df778-47d7-499e-8639-dee4480c07de",
"name": "Edit Fields2",
"alwaysOutputData": true
},
{
"parameters": {
"inputDataFieldName": "submission_file",
"name": "={{ $binary.submission_file.fileName || 'submission.pdf' }}",
"driveId": {
"__rl": true,
"mode": "list",
"value": "My Drive"
},
"folderId": {
"__rl": true,
"value": "1wOZ8pKLYH-mzXpKavQsH8Y7HuMGjkECP",
"mode": "list",
"cachedResultName": "n8n",
"cachedResultUrl": "https://drive.google.com/drive/folders/1wOZ8pKLYH-mzXpKavQsH8Y7HuMGjkECP"
},
"options": {
"ocrLanguage": "vi"
}
},
"type": "n8n-nodes-base.googleDrive",
"typeVersion": 3,
"position": [
1120,
608
],
"id": "e76fb731-8844-4dea-8427-d0219db7a621",
"name": "Upload file",
"alwaysOutputData": true,
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "download",
"fileId": {
"__rl": true,
"value": "={{ $json.id }}",
"mode": "id"
},
"options": {}
},
"id": "4a216fec-e87a-4e6e-aef4-b8b72ca88f47",
"name": "Import PDF",
"type": "n8n-nodes-base.googleDrive",
"position": [
1296,
416
],
"typeVersion": 3,
"credentials": {
"googleDriveOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const extract = $('exam_detail').first()?.json?.extract_json;\nconst submission = $('add asignment').first()?.json;\nconst formData = $('Edit Fields2').first()?.json || {};\n\nif (!submission?.id) {\n throw new Error('Khong tim thay submission id de cap nhat OCR result');\n}\n\nconst submissionId = Number(submission.id);\nconst examId = Number(submission.exam_id || 0);\nconst studentCode = String(formData['M\u00e3 sinh vi\u00ean'] || '').replace(/'/g, \"''\");\nconst studentName = String(formData.name || '').replace(/'/g, \"''\");\nconst classCode = String(formData.class_code || '').replace(/'/g, \"''\");\nconst extractJsonStr = JSON.stringify(extract || []).replace(/'/g, \"''\");\nconst payload = JSON.stringify({ extract_json: extract || [] }).replace(/'/g, \"''\");\n\nconst sql = `\nUPDATE submissions\nSET\n submission_extract = '${extractJsonStr}'::jsonb,\n status = 'extracted',\n updated_at = NOW()\nWHERE id = ${submissionId};\n\nINSERT INTO system_logs (\n log_type,\n ref_table,\n ref_id,\n exam_id,\n submission_id,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n model_name,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'submission_extract',\n 'submissions',\n ${submissionId},\n ${examId},\n ${submissionId},\n NULLIF('${studentCode}', ''),\n NULLIF('${studentName}', ''),\n NULLIF('${classCode}', ''),\n '{{ $execution.id }}',\n 'mistral-ocr-latest + gpt-4.1-mini',\n 'success',\n 'OCR va trich xuat JSON thanh cong - luu vao submission_extract',\n '${payload}'::jsonb,\n NOW()\n);`;\n\nreturn [{ json: { id: submissionId, sql } }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2288,
992
],
"id": "4838cb97-7a8f-4df9-9165-e9da4be49380",
"name": "Code in JavaScript1",
"alwaysOutputData": true
},
{
"parameters": {
"jsCode": "const textData = $input.first().json.text || '[]';\nconst extractJson = JSON.parse(textData);\n\nreturn [\n {\n json: {\n extract_json: extractJson\n }\n }\n];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
2928,
448
],
"id": "5abd6203-1c8f-4c21-940e-32e954df41cc",
"name": "exam_detail"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "22560f41-2c33-46a7-81b7-d127f27376e9",
"name": "id",
"value": "={{ $('Code in JavaScript1').item.json.id }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2976,
944
],
"id": "2edc3a16-a518-4413-b25c-d28f4d4b06de",
"name": "Edit Fields3"
},
{
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "1xUS5MQzZygPPJ0VT0meaEIILSe5PdMir0syofwVH9Mo",
"mode": "list",
"cachedResultName": "n8n_exam",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xUS5MQzZygPPJ0VT0meaEIILSe5PdMir0syofwVH9Mo/edit?usp=drivesdk"
},
"sheetName": {
"__rl": true,
"value": "gid=0",
"mode": "list",
"cachedResultName": "Trang t\u00ednh1",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1xUS5MQzZygPPJ0VT0meaEIILSe5PdMir0syofwVH9Mo/edit#gid=0"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"stt": "={{ $json.id }}",
"th\u00eam th\u00e0nh c\u00f4ng": "true"
},
"matchingColumns": [
"stt"
],
"schema": [
{
"id": "stt",
"displayName": "stt",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "T\u00ean",
"displayName": "T\u00ean",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "File",
"displayName": "File",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "Tr\u1ea1ng th\u00e1i",
"displayName": "Tr\u1ea1ng th\u00e1i",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": true
},
{
"id": "th\u00eam th\u00e0nh c\u00f4ng",
"displayName": "th\u00eam th\u00e0nh c\u00f4ng",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
3184,
944
],
"id": "6009a943-2ec6-48ef-8198-739ed461e0f8",
"name": "Append or update row in sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "{{ $json.sql }}",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
2736,
928
],
"id": "1317d3d6-de19-428c-be22-99a2b8192667",
"name": "Execute a SQL query",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "d0add232-c484-41d3-b3ab-97a3de4e8bdc",
"name": "sql",
"value": "={{ $json.sql }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
2544,
928
],
"id": "e292c069-8866-42fe-b7ae-9c06a85d09b3",
"name": "Edit Fields4"
},
{
"parameters": {
"operation": "executeQuery",
"query": "SELECT id, class_code, subject_code, status\nFROM exams\nWHERE id = {{ Number($('Edit Fields2').item.json.exam_id || 0) }}\nLIMIT 1;",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
864,
1264
],
"id": "89de067d-78ac-4572-9da8-8f75790505c8",
"name": "Execute a SQL query1",
"alwaysOutputData": true,
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"jsCode": "const exam = $input.first()?.json || {};\nconst examId = Number($('Edit Fields2').first()?.json?.exam_id || 0);\n\nif (!Number.isFinite(examId) || examId <= 0) {\n throw new Error('exam_id khong hop le hoac bi thieu trong request');\n}\n\nif (!exam.id) {\n throw new Error(`Khong tim thay exam voi exam_id=${examId}`);\n}\n\nif (String(exam.status || '').toLowerCase() !== 'active') {\n throw new Error(`Exam ${examId} khong o trang thai active`);\n}\n\nreturn $input.all();"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1072,
960
],
"id": "8c1f4ba5-0aac-44fe-81fe-120a02849730",
"name": "Validate active exam"
},
{
"parameters": {
"jsCode": "const item = $input.first();\nconst binary = $('Validate PDF file').item.binary || {};\nreturn [{ json: item.json, binary }];"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1072,
416
],
"id": "6a515d9d-1ecf-4236-952e-c159d4901acd",
"name": "Restore binary"
},
{
"parameters": {
"operation": "executeQuery",
"query": "WITH upsert_submission AS (\n -- Th\u1eed UPDATE tr\u01b0\u1edbc n\u1ebfu submission \u0111\u00e3 t\u1ed3n t\u1ea1i (c\u00f9ng exam_id + student_code)\n UPDATE submissions\n SET\n student_name = '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n class_code = COALESCE(NULLIF('{{ ($('Validate active exam').item.json.class_code || '').replace(/'/g, \"''\") }}', ''), '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}'),\n subject_code = '{{ ($('Edit Fields2').item.json.subject_code || '').replace(/'/g, \"''\") }}',\n submission_file_path = '{{ ($('Upload file').item.json.webViewLink || $('Upload file').item.json.id || '').replace(/'/g, \"''\") }}',\n submitted_at = NOW(),\n status = 'extracting',\n updated_at = NOW()\n WHERE exam_id = {{ $('Validate active exam').item.json.id }}\n AND student_code = '{{ ($('Edit Fields2').item.json['M\u00e3 sinh vi\u00ean'] || '').replace(/'/g, \"''\") }}'\n RETURNING id, exam_id\n),\ninsert_submission AS (\n -- N\u1ebfu UPDATE kh\u00f4ng match \u2192 INSERT b\u1ea3n ghi m\u1edbi (ch\u1ec9 khi KH\u00d4NG t\u1ed3n t\u1ea1i trong upsert_submission)\n INSERT INTO submissions (\n exam_id,\n student_code,\n student_name,\n class_code,\n subject_code,\n submission_file_path,\n submitted_at,\n status,\n created_at,\n updated_at\n )\n SELECT\n {{ $('Validate active exam').item.json.id }},\n '{{ ($('Edit Fields2').item.json['M\u00e3 sinh vi\u00ean'] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n COALESCE(NULLIF('{{ ($('Validate active exam').item.json.class_code || '').replace(/'/g, \"''\") }}', ''), '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}'),\n '{{ ($('Edit Fields2').item.json.subject_code || '').replace(/'/g, \"''\") }}',\n '{{ ($('Upload file').item.json.webViewLink || $('Upload file').item.json.id || '').replace(/'/g, \"''\") }}',\n NOW(),\n 'extracting',\n NOW(),\n NOW()\n WHERE NOT EXISTS (SELECT 1 FROM upsert_submission)\n RETURNING id, exam_id\n)\n-- Tr\u1ea3 v\u1ec1 ID c\u1ee7a submission (t\u1eeb UPDATE ho\u1eb7c INSERT)\nSELECT id, exam_id FROM upsert_submission\nUNION ALL\nSELECT id, exam_id FROM insert_submission\nLIMIT 1;",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
1680,
864
],
"id": "150844d3-79fe-40cb-a73b-d80c6f9ab3e0",
"name": "add asignment",
"alwaysOutputData": true,
"retryOnFail": false,
"executeOnce": false,
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"mode": "append",
"options": {}
},
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
2304,
784
],
"id": "40d30599-ac9c-4515-88d1-a7f86f124a8b",
"name": "Merge"
},
{
"parameters": {
"operation": "executeQuery",
"query": "=INSERT INTO system_logs (\n log_type,\n ref_table,\n ref_id,\n exam_id,\n submission_id,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n model_name,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'data_merged',\n 'submissions',\n {{ $('add asignment').item.json.id }},\n {{ $('add asignment').item.json.exam_id }},\n {{ $('add asignment').item.json.id }},\n '{{ ($('Edit Fields2').item.json[\"M\u00e3 sinh vi\u00ean\"] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}',\n '{{ $execution.id }}',\n 'mistral-ocr-latest + gpt-4.1-mini',\n 'success',\n 'Submission and extraction data merged - ready for final update',\n jsonb_build_object('submission_id', {{ $('add asignment').item.json.id }}, 'exam_id', {{ $('add asignment').item.json.exam_id }}, 'answers_count', {{ ($('exam_detail').item.json.extract_json || []).length }}),\n NOW()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
2288,
1152
],
"id": "log-data-aggregated",
"name": "Log Data Merged",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "=INSERT INTO system_logs (\n log_type,\n ref_table,\n exam_id,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'pdf_validation',\n 'submissions',\n {{ $('Validate active exam').item.json.id }},\n '{{ ($('Edit Fields2').item.json[\"M\u00e3 sinh vi\u00ean\"] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}',\n '{{ $execution.id }}',\n 'success',\n 'PDF file validated successfully',\n jsonb_build_object('exam_id', {{ $('Validate active exam').item.json.id }}, 'class_code', '{{ ($('Validate active exam').item.json.class_code || '').replace(/'/g, \"''\") }}'),\n NOW()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
160,
900
],
"id": "log-pdf-validation",
"name": "Log PDF Validation",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "=INSERT INTO system_logs (\n log_type,\n ref_table,\n ref_id,\n exam_id,\n submission_id,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'submission_created',\n 'submissions',\n {{ $json.id }},\n {{ $json.exam_id }},\n {{ $json.id }},\n '{{ ($('Edit Fields2').item.json[\"M\u00e3 sinh vi\u00ean\"] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}',\n '{{ $execution.id }}',\n 'success',\n 'Submission record created/updated',\n jsonb_build_object('submission_id', {{ $json.id }}, 'exam_id', {{ $json.exam_id }}, 'file_path', '{{ ($('Upload file').item.json.webViewLink || $('Upload file').item.json.id || '').replace(/'/g, \"''\") }}'),\n NOW()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
368,
900
],
"id": "log-submission-created",
"name": "Log Submission Created",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "=INSERT INTO system_logs (\n log_type,\n ref_table,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n model_name,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'ocr_started',\n 'submissions',\n '{{ ($('Edit Fields2').item.json[\"M\u00e3 sinh vi\u00ean\"] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}',\n '{{ $execution.id }}',\n 'mistral-ocr-latest',\n 'processing',\n 'OCR processing started - file uploaded to Mistral',\n jsonb_build_object('mistral_file_id', '{{ $json.id }}'),\n NOW()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
1504,
560
],
"id": "log-ocr-started",
"name": "Log OCR Started",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"operation": "executeQuery",
"query": "=INSERT INTO system_logs (\n log_type,\n ref_table,\n student_code,\n student_name,\n class_code,\n workflow_execution_id,\n model_name,\n status,\n message,\n response_payload,\n created_at\n) VALUES (\n 'extraction_completed',\n 'submissions',\n '{{ ($('Edit Fields2').item.json[\"M\u00e3 sinh vi\u00ean\"] || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.name || '').replace(/'/g, \"''\") }}',\n '{{ ($('Edit Fields2').item.json.class_code || '').replace(/'/g, \"''\") }}',\n '{{ $execution.id }}',\n 'gpt-4.1-mini',\n 'success',\n 'Answer extraction completed - {{ ($json.extract_json || []).length }} answers extracted',\n '{{ JSON.stringify({ extract_json: $json.extract_json || [] }).replace(/'/g, \"''\") }}'::jsonb,\n NOW()\n);",
"options": {}
},
"type": "n8n-nodes-base.postgres",
"typeVersion": 2.6,
"position": [
2928,
608
],
"id": "log-extraction-completed",
"name": "Log Extraction Completed",
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
}
],
"connections": {
"Mistral Upload": {
"main": [
[
{
"node": "Mistral Signed URL",
"type": "main",
"index": 0
},
{
"node": "Log OCR Started",
"type": "main",
"index": 0
}
]
]
},
"Mistral Signed URL": {
"main": [
[
{
"node": "Mistral DOC OCR",
"type": "main",
"index": 0
}
]
]
},
"Mistral DOC OCR": {
"main": [
[
{
"node": "Edit Fields",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields": {
"main": [
[
{
"node": "Message a model",
"type": "main",
"index": 0
}
]
]
},
"Message a model": {
"main": [
[
{
"node": "Edit Fields1",
"type": "main",
"index": 0
}
]
]
},
"On API submission": {
"main": [
[
{
"node": "Validate PDF file",
"type": "main",
"index": 0
}
]
]
},
"Validate PDF file": {
"main": [
[
{
"node": "Edit Fields2",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields2": {
"main": [
[
{
"node": "Execute a SQL query1",
"type": "main",
"index": 0
}
]
]
},
"Upload file": {
"main": [
[
{
"node": "Import PDF",
"type": "main",
"index": 0
}
]
]
},
"Import PDF": {
"main": [
[
{
"node": "Mistral Upload",
"type": "main",
"index": 0
},
{
"node": "add asignment",
"type": "main",
"index": 0
}
]
]
},
"Code in JavaScript1": {
"main": [
[
{
"node": "Edit Fields4",
"type": "main",
"index": 0
}
]
]
},
"exam_detail": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
},
{
"node": "Log Extraction Completed",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields3": {
"main": [
[
{
"node": "Append or update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Execute a SQL query": {
"main": [
[
{
"node": "Edit Fields3",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields4": {
"main": [
[
{
"node": "Execute a SQL query",
"type": "main",
"index": 0
}
]
]
},
"Execute a SQL query1": {
"main": [
[
{
"node": "Validate active exam",
"type": "main",
"index": 0
}
]
]
},
"Validate active exam": {
"main": [
[
{
"node": "Restore binary",
"type": "main",
"index": 0
},
{
"node": "Log PDF Validation",
"type": "main",
"index": 0
}
]
]
},
"Restore binary": {
"main": [
[
{
"node": "Upload file",
"type": "main",
"index": 0
}
]
]
},
"add asignment": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
},
{
"node": "Log Submission Created",
"type": "main",
"index": 0
}
]
]
},
"Edit Fields1": {
"main": [
[
{
"node": "exam_detail",
"type": "main",
"index": 0
}
]
]
},
"Merge": {
"main": [
[
{
"node": "Code in JavaScript1",
"type": "main",
"index": 0
},
{
"node": "Log Data Merged",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "525cd2e4-9b5c-487c-b109-0940501b3f24",
"id": "33Gz1ni6vQlwrCXL",
"tags": []
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleDriveOAuth2ApigoogleSheetsOAuth2ApimistralCloudApiopenAiApipostgres
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
upload_answer. Uses httpRequest, openAi, googleDrive, googleSheets. Webhook trigger; 27 nodes.
Source: https://github.com/dungthieuIT98/n8n/blob/f6887fe7bde96eab8e412c836e1cb7c258cd2322/workflows/upload_answer.json — 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 workflow automates the transformation of spreadsheet data into professional charts and graphs using AI-driven analysis. Triggered via Slack, it processes uploaded files (Excel, CSV, Google Sh
Scheduled processes retrieve customer feedback from multiple channels. The system performs sentiment analysis to classify tone, then uses OpenAI models to extract themes, topics, and urgency indicator
This workflow is designed for Customer Success Managers, Growth Teams, and SaaS Business Owners who want to proactively reduce churn using AI. It automates the analysis of customer health and the deli
This workflow functions as an automated "Chief Wellness Officer," helping HR teams and managers prevent employee burnout before it happens. It aggregates data from communication channels and work tool
DAta lake 1. Uses openAi, httpRequest, googleSheets, mongoDb. Webhook trigger; 23 nodes.