{"meta":{"templateId":"vntana-attributes-from-attachment","description":"Watches a VNTANA product's attachments on a timer. When a new .xlsx file named attributes_completed_*.xlsx appears, reads two columns (attribute_key / attribute_value) and replaces the product's attributes entirely. Works for any VNTANA product and any attribute keys. Requires VNTANA_EMAIL and VNTANA_PASSWORD n8n variables.","templateCredsSetupCompleted":false},"name":"VNTANA — Update Product Attributes from Attachment","nodes":[{"parameters":{"content":"## VNTANA — Update Product Attributes from Attachment\n\n### How it works\n\nThis workflow periodically checks VNTANA for a new attachment matching a configured filename prefix. When it finds a new file, it downloads and parses the spreadsheet, converts the rows into product attribute data, and updates VNTANA products through the API. The flow avoids unnecessary work by stopping at the conditional branch when no new file is found.\n\n### Setup steps\n\n- Fill in the Config node with the required VNTANA values, including API credentials, filename prefix, and any product or attribute configuration referenced by the code.\n- Ensure the VNTANA API authentication request in the Auth node is configured with the correct endpoint, headers, and credential values.\n- Verify the attachment search, download, and product update HTTP Request nodes point to the correct VNTANA API environment and use the token returned by Auth.\n- Confirm the spreadsheet format matches the parsing and Build Attributes code expectations, including required column names and data types.\n- Activate the workflow so the Every 15 Minutes schedule trigger can run automatically.\n\n### Customization\n\nAdjust the schedule frequency, filename prefix, spreadsheet column mapping, or attribute-building logic to match different import cadences and product data formats.","width":480,"height":880},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[-368,96],"id":"6b792bf1-addd-4588-9eb1-8a16e120fc82","name":"Sticky Note"},{"parameters":{"content":"## Initialize scheduled run\n\nDefines the required workflow configuration, runs on a 15-minute schedule, and merges the trigger with config values so downstream nodes can use both.","width":432,"height":528,"color":7},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[192,128],"id":"860c3607-fa72-4674-861a-5b1581f064b2","name":"Sticky Note1"},{"parameters":{"content":"## Authenticate and search\n\nAuthenticates with the VNTANA API, then searches VNTANA attachments for files matching the configured criteria.","width":416,"height":320,"color":7},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[656,224],"id":"62cd813b-068c-4295-898f-392e2aecf44b","name":"Sticky Note2"},{"parameters":{"content":"## Check latest attachment\n\nSelects the newest matching attachment and branches only when a new file is available to process.","width":416,"height":320,"color":7},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1120,224],"id":"e21e24ad-ce57-4d67-b0c6-b9bf43d42fe2","name":"Sticky Note3"},{"parameters":{"content":"## Download and parse file\n\nDownloads the selected attachment from VNTANA storage and parses the spreadsheet contents into rows for processing.","width":416,"height":320,"color":7},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[1568,96],"id":"3878ce62-c493-432a-a718-636a040e5835","name":"Sticky Note4"},{"parameters":{"content":"## Build and update attributes\n\nTransforms spreadsheet rows into the product attributes payload and sends the update back to VNTANA via the API.","width":416,"height":320,"color":7},"type":"n8n-nodes-base.stickyNote","typeVersion":1,"position":[2016,96],"id":"f9bb6202-b96b-450c-b550-4871bc5451e5","name":"Sticky Note5"},{"id":"config","name":"Configure Values","type":"n8n-nodes-base.code","notes":"Edit the four values at the top of this node.","position":[240,300],"parameters":{"jsCode":"// ── CONFIGURE THESE FOUR VALUES ──────────────────────────\nconst CONFIG = {\n  productUuid: 'YOUR_PRODUCT_UUID',\n  clientUuid:  'YOUR_WORKSPACE_UUID',\n  productName: 'Your Product Name',\n  filePrefix:  'attributes_completed_'\n};\n// ──────────────────────────────────────────────────────────\nreturn [{ json: CONFIG }];"},"typeVersion":2},{"id":"schedule","name":"When Every 15 Minutes","type":"n8n-nodes-base.scheduleTrigger","position":[240,480],"parameters":{"rule":{"interval":[{"field":"minutes","minutesInterval":15}]}},"typeVersion":1.2},{"id":"mergeStart","name":"Merge Config and Trigger","type":"n8n-nodes-base.code","position":[480,380],"parameters":{"jsCode":"// Merge trigger + config so downstream nodes have both\nconst config = $node['Config'].json;\nreturn [{ json: config }];"},"typeVersion":2},{"id":"auth","name":"Post Auth to VNTANA","type":"n8n-nodes-base.httpRequest","position":[700,380],"parameters":{"url":"https://api-platform.vntana.com/v1/auth/login","body":{"email":"={{ $vars.VNTANA_EMAIL }}","password":"={{ $vars.VNTANA_PASSWORD }}"},"method":"POST","options":{},"sendBody":true,"contentType":"json"},"typeVersion":4.2},{"id":"searchAttachments","name":"Post Search Attachments","type":"n8n-nodes-base.httpRequest","position":[920,380],"parameters":{"url":"https://api-platform.vntana.com/v1/attachments/search","body":{"page":1,"size":50},"method":"POST","options":{},"sendBody":true,"sendQuery":true,"contentType":"json","sendHeaders":true,"queryParameters":{"parameters":[{"name":"productUuid","value":"={{ $node['Config'].json.productUuid }}"},{"name":"entityType","value":"PRODUCT"}]},"headerParameters":{"parameters":[{"name":"X-AUTH-TOKEN","value":"={{ $json.response.token }}"},{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2},{"id":"checkNewFile","name":"Check Newest Attachment","type":"n8n-nodes-base.code","position":[1168,384],"parameters":{"jsCode":"// Find the newest attachment matching the filename prefix.\n// Skip if already processed (UUID stored in workflow static data).\n\nconst prefix      = $node['Config'].json.filePrefix.toLowerCase();\nconst attachments = $input.first().json.response?.grid ?? [];\n\nconst candidates = attachments\n  .filter(a => (a.name ?? '').toLowerCase().startsWith(prefix) &&\n               (a.name ?? '').toLowerCase().endsWith('.xlsx'))\n  .sort((a, b) => new Date(b.created ?? 0) - new Date(a.created ?? 0));\n\nif (!candidates.length) {\n  return [{ json: { skip: true, reason: 'No matching attachment found.' } }];\n}\n\nconst latest     = candidates[0];\nconst staticData = $getWorkflowStaticData('global');\n\nif (latest.uuid === staticData.lastProcessedUuid) {\n  return [{ json: { skip: true, reason: `Already processed: ${latest.name}` } }];\n}\n\nstaticData.lastProcessedUuid = latest.uuid;\nstaticData.lastProcessedAt   = new Date().toISOString();\n\nreturn [{ json: { skip: false, blobId: latest.blobId, name: latest.name } }];"},"typeVersion":2},{"id":"ifNewFile","name":"If New File Exists","type":"n8n-nodes-base.if","notes":"True branch: new file found. False branch: nothing to do.","position":[1392,384],"parameters":{"options":{},"conditions":{"options":{"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"skip-check","operator":{"type":"boolean","operation":"equals"},"leftValue":"={{ $json.skip }}","rightValue":false}]}},"typeVersion":2},{"id":"downloadFile","name":"Fetch Attachment File","type":"n8n-nodes-base.httpRequest","position":[1616,256],"parameters":{"url":"=https://api-platform.vntana.com/v1/storage/load/asset/model?blobId={{ $json.blobId }}&clientUuid={{ $node['Config'].json.clientUuid }}","options":{"response":{"response":{"responseFormat":"file","outputPropertyName":"data"}}},"sendHeaders":true,"headerParameters":{"parameters":[{"name":"X-AUTH-TOKEN","value":"={{ $node['Auth'].json.response.token }}"}]}},"typeVersion":4.2},{"id":"parseSpreadsheet","name":"Parse Spreadsheet Data","type":"n8n-nodes-base.spreadsheetFile","notes":"Reads every row as a JSON object. Expects columns: attribute_key | attribute_value","position":[1840,256],"parameters":{"options":{},"operation":"toJson","binaryPropertyName":"data"},"typeVersion":2},{"id":"buildAttributes","name":"Build Attribute Array","type":"n8n-nodes-base.code","notes":"Converts rows to VNTANA attributes array. Values can be plain strings or JSON strings.","position":[2064,256],"parameters":{"jsCode":"// Build the attributes array from the spreadsheet rows.\n// Each row must have: attribute_key | attribute_value\n// This REPLACES all existing product attributes — nothing is preserved.\n\nconst rows = $input.all().map(i => i.json);\nconst attributes = [];\n\nfor (const row of rows) {\n  const key   = String(row['attribute_key']   ?? '').trim();\n  const value = String(row['attribute_value'] ?? '').trim();\n  if (!key) continue;\n  attributes.push({ name: key, value });\n}\n\nif (!attributes.length) {\n  throw new Error('Spreadsheet has no valid rows. Check column headers: attribute_key | attribute_value');\n}\n\nconst config = $node['Config'].json;\nreturn [{ json: {\n  uuid:       config.productUuid,\n  name:       config.productName,\n  attributes\n} }];"},"typeVersion":2},{"id":"writeAttributes","name":"Update Product Attributes","type":"n8n-nodes-base.httpRequest","notes":"PUTs {uuid, name, attributes} — replaces all product attributes.","position":[2288,256],"parameters":{"url":"https://api-platform.vntana.com/v1/products","body":"={{ JSON.stringify($json) }}","method":"PUT","options":{},"sendBody":true,"contentType":"json","sendHeaders":true,"headerParameters":{"parameters":[{"name":"X-AUTH-TOKEN","value":"={{ $node['Auth'].json.response.token }}"},{"name":"Content-Type","value":"application/json"}]}},"typeVersion":4.2}],"settings":{"executionOrder":"v1"},"staticData":null,"connections":{"Post Auth to VNTANA":{"main":[[{"node":"Post Search Attachments","type":"main","index":0}]]},"Merge Config and Trigger":{"main":[[{"node":"Post Auth to VNTANA","type":"main","index":0}]]},"Configure Values":{"main":[[{"node":"Merge Config and Trigger","type":"main","index":0}]]},"If New File Exists":{"main":[[{"node":"Fetch Attachment File","type":"main","index":0}],[]]},"Build Attribute Array":{"main":[[{"node":"Update Product Attributes","type":"main","index":0}]]},"When Every 15 Minutes":{"main":[[{"node":"Merge Config and Trigger","type":"main","index":0}]]},"Parse Spreadsheet Data":{"main":[[{"node":"Build Attribute Array","type":"main","index":0}]]},"Check Newest Attachment":{"main":[[{"node":"If New File Exists","type":"main","index":0}]]},"Post Search Attachments":{"main":[[{"node":"Check Newest Attachment","type":"main","index":0}]]},"Fetch Attachment File":{"main":[[{"node":"Parse Spreadsheet Data","type":"main","index":0}]]}}}
