This workflow corresponds to n8n.io template #17320 — we link there as the canonical source.
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": "Wq0zGSjNncLFSr4m",
"name": "SUBMISSION COPY - Gemini Re-render + Auto-Tagging",
"tags": [],
"nodes": [
{
"id": "e64d3161-0554-4e20-83e7-3a2f79c9a25f",
"name": "When clicking \u2018Execute workflow\u2019",
"type": "n8n-nodes-base.manualTrigger",
"position": [
0,
0
],
"parameters": {},
"typeVersion": 1
},
{
"id": "dbc65fcb-a0b6-4a18-99b1-bc7e1d4f37a0",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
208,
0
],
"parameters": {
"path": "b3b1852c-28e8-4598-9972-adeba20a0b38",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "4f18a039-6e84-4689-b5d1-478f082b1327",
"name": "Gemini Studio Re-render",
"type": "n8n-nodes-base.httpRequest",
"onError": "continueErrorOutput",
"position": [
1472,
0
],
"parameters": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({\n contents: [{\n parts: [\n { text: \"Treat the uploaded image as the exact reference for the mechanical assembly. Preserve geometry, proportions, part count, component placement, fastener positions, casting details, fins, pulleys, belts, bolts, and overall silhouette exactly as shown in the reference. Do not redesign, restyle, reinterpret, add, or remove any component. Match the camera angle, framing, rotation, and composition of the reference image exactly. Re-render the assembly as a real-world studio product photograph in the style of premium industrial and engineering catalog photography. Render the materials physically accurately: cast and machined aluminum with a fine matte-to-satin metallic finish and subtle micro-texture; steel and hardware with brighter specular reflectivity; cast housings with slightly rougher surfaces than machined faces. Read the form directly from the reference and apply realistic metal shading, not paint or plastic. Place the assembly on a pure white seamless background. Apply soft, large-source studio key lighting from the upper left, gentle fill from camera right, and a subtle rim light to separate the part from the background and reveal edge detail on fins and machined surfaces. Add a realistic soft contact shadow directly beneath the assembly, grounding it to a flat surface, darker where parts meet the surface and fading outward. Sharp focus across the entire assembly with deep depth of field, approximately f/8 to f/11. 85mm lens compression. Neutral white balance. Crisp clean specular highlights on metal without blowing out detail. Negative constraints: do not alter geometry, proportions, or component layout. Do not turn metal into plastic, chrome, or painted surfaces. Do not add color tints, environmental staging, props, gradients, vignettes, reflections of surroundings, or background elements. Do not introduce motion blur, lens flare, HDR halos, illustration, or stylization. Do not add or remove any part, bolt, or feature.\" },\n { inlineData: { mimeType: $json.mimeType, data: $json.base64 } }\n ]\n }],\n generationConfig: { responseModalities: [\"IMAGE\"] }\n}) }}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth"
},
"credentials": {
"httpQueryAuth": {
"name": "<your credential>"
}
},
"retryOnFail": true,
"typeVersion": 4.4
},
{
"id": "f987c2d1-0fa4-422e-b204-76735665e74c",
"name": "Prep Image for Gemini",
"type": "n8n-nodes-base.code",
"position": [
944,
0
],
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n const buffer = await this.helpers.getBinaryDataBuffer(i, 'data');\n const base64 = buffer.toString('base64');\n \n results.push({\n json: {\n base64: base64,\n mimeType: item.binary.data.mimeType,\n fileName: item.binary.data.fileName\n },\n binary: item.binary\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "89851daa-fbe9-4dbc-85eb-526411779f49",
"name": "Extract Gemini Image",
"type": "n8n-nodes-base.code",
"position": [
1712,
-96
],
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\n\nfor (let i = 0; i < items.length; i++) {\n const item = items[i];\n const part = item.json.candidates[0].content.parts.find(p => p.inlineData);\n \n if (!part) {\n continue; // skip if no image came back\n }\n \n const base64 = part.inlineData.data;\n const mimeType = part.inlineData.mimeType || 'image/png';\n const buffer = Buffer.from(base64, 'base64');\n const fileName = `studio_render_${i}.png`;\n \n results.push({\n json: {\n fileName: fileName,\n fileSize: buffer.length,\n mimeType: mimeType\n },\n binary: {\n data: await this.helpers.prepareBinaryData(buffer, fileName, mimeType)\n }\n });\n}\n\nreturn results;"
},
"typeVersion": 2
},
{
"id": "f95e2813-a8bf-46a1-be5e-4a522b1801df",
"name": "Limit",
"type": "n8n-nodes-base.limit",
"position": [
1168,
-384
],
"parameters": {},
"typeVersion": 1
},
{
"id": "8b7a2e4f-e66a-4460-8de1-b61046c5897d",
"name": "Gemini Tagging",
"type": "n8n-nodes-base.httpRequest",
"position": [
1392,
-384
],
"parameters": {
"url": "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent",
"method": "POST",
"options": {},
"jsonBody": "={\n \"contents\": [{\n \"parts\": [\n {\n \"text\": \"You are a product cataloging assistant. Look at this product render and generate a list of 10-15 free-form descriptive tags suitable for an e-commerce product catalog. Tags should describe: product category, style, materials, colors, design features, and likely use cases. Use Title Case (capitalize the first letter of each word), prefer single-word or two-word tags, no brand names, no subjective words like 'beautiful' or 'amazing'. Return ONLY valid JSON in this exact format with no other text:\\n{\\\"tags\\\": [\\\"tag1\\\", \\\"tag2\\\", \\\"...\\\"]}\"\n },\n {\n \"inlineData\": {\n \"mimeType\": \"{{ $json.mimeType }}\",\n \"data\": \"{{ $json.base64 }}\"\n }\n }\n ]\n }],\n \"generationConfig\": {\n \"responseMimeType\": \"application/json\"\n }\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "httpQueryAuth",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"credentials": {
"httpQueryAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "1f7cd277-5330-4200-9258-fbad96400fb1",
"name": "Resolve Tags & Update Asset",
"type": "n8n-nodes-base.code",
"position": [
1568,
-384
],
"parameters": {
"jsCode": "// Org-scoped VNTANA API token (X-AUTH-TOKEN). Get one via the VNTANA login + refresh-token flow.\nconst REFRESH_TOKEN = 'YOUR_VNTANA_API_TOKEN';\nconst CLIENT_UUID = 'YOUR_WORKSPACE_UUID';\nconst PRODUCT_UUID = $('Webhook').first().json.body.product.uuid;\n\nconst headers = {\n 'x-auth-token': `Bearer ${REFRESH_TOKEN}`,\n 'Content-Type': 'application/json'\n};\n\nconst tagText = $('Gemini Tagging').first().json.candidates[0].content.parts[0].text;\nconst tagNames = JSON.parse(tagText).tags;\n\nconst tagUuids = [];\nfor (const name of tagNames) {\n let uuid = null;\n \n try {\n const resp = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api-platform.vntana.com/v1/tags/create',\n headers,\n body: { clientUuid: CLIENT_UUID, name },\n json: true\n });\n if (resp && resp.success && resp.response && resp.response.uuid) {\n uuid = resp.response.uuid;\n }\n } catch (e) {\n // 409 = already exists, fall through to search\n }\n \n if (!uuid) {\n try {\n const resp = await this.helpers.httpRequest({\n method: 'POST',\n url: 'https://api-platform.vntana.com/v1/tags/search',\n headers,\n body: { clientUuid: CLIENT_UUID, page: 1, searchTerm: name, size: 10 },\n json: true\n });\n const grid = (resp && resp.response && resp.response.grid) || [];\n const exact = grid.find(t => t.name === name);\n if (exact) uuid = exact.uuid;\n } catch (e) {}\n }\n \n if (uuid) tagUuids.push(uuid);\n}\n\n// Get current asset for the PUT body\nconst assetResp = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://api-platform.vntana.com/v1/products/${PRODUCT_UUID}`,\n headers,\n json: true\n});\nconst asset = assetResp.response;\n\nconst existingUuids = (asset.tags || []).map(t => t.uuid);\n// Cap at 10 total tags, prioritizing newly generated AI tags\nconst merged = [...new Set([...tagUuids, ...existingUuids])];\nconst allTagUuids = merged.slice(0, 10);\n\nconst updateBody = {\n uuid: PRODUCT_UUID,\n clientUuid: asset.clientUuid,\n name: asset.name,\n description: asset.description || '',\n tagsUuids: allTagUuids,\n locationsUuids: [],\n attributes: asset.attributes || {},\n pipelineUuid: asset.pipelineUuid,\n presetUuid: asset.presetUuid || '',\n assetType: asset.assetType || 'THREE_D',\n modelOpsParameters: asset.modelOpsParameters || {},\n publishToStatus: asset.status || 'LIVE_PUBLIC',\n projectsUuids: [],\n project: null,\n deleteAsset: false\n};\n\nconst updateResp = await this.helpers.httpRequest({\n method: 'PUT',\n url: 'https://api-platform.vntana.com/v1/products',\n headers,\n body: updateBody,\n json: true\n});\n\nreturn [{\n json: {\n tagsAdded: tagNames,\n tagsResolvedUuids: tagUuids,\n finalTagsOnAsset: allTagUuids,\n updateSuccess: updateResp && updateResp.success,\n updateResponse: updateResp\n }\n}];"
},
"typeVersion": 2
},
{
"id": "b9ead8c0-1280-4498-ab7c-15fc152de79d",
"name": "Download renders",
"type": "n8n-nodes-vntana.vntana",
"position": [
432,
0
],
"parameters": {
"resource": "render",
"clientUuid": "YOUR_WORKSPACE_UUID",
"downloadAll": true,
"productUuid": "={{ $('Webhook').first().json.body.product.uuid }}"
},
"credentials": {
"vntanaApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "bc15585d-8bb5-4922-b8a9-a26900a92c9e",
"name": "Skip Images Already Processed",
"type": "n8n-nodes-base.code",
"position": [
704,
0
],
"parameters": {
"jsCode": "return $input.all().filter(item => {\n const name = (item.binary && item.binary.data && item.binary.data.fileName) || '';\n return !name.startsWith('studio_render_');\n});"
},
"typeVersion": 2
},
{
"id": "4917c8e3-378c-4494-ac2d-aef83c1f1a0b",
"name": "Upload a render",
"type": "n8n-nodes-vntana.vntana",
"position": [
1920,
-96
],
"parameters": {
"options": {
"fileName": "={{ $json.fileName }}"
},
"resource": "render",
"operation": "upload",
"clientUuid": "YOUR_WORKSPACE_UUID",
"productUuid": "={{ $('Webhook').first().json.body.product.uuid }}"
},
"credentials": {
"vntanaApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "sticky-sticky-note",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-640,
-512
],
"parameters": {
"width": 520,
"height": 949,
"content": "## Generate AI studio renders and auto-tag VNTANA 3D assets with Gemini\n\n### How it works\n\nWhen an asset is published in VNTANA, this workflow turns its raw platform renders into marketing-grade imagery and searchable metadata in one pass. It downloads the asset's renders, skips any that were already processed (so re-publishing never double-bills), then runs two Gemini stages in parallel:\n\n1. **Studio re-render**: sends the render to Gemini's image model with an editable style prompt that preserves the product's exact geometry and framing, and uploads the result back to VNTANA as a new render.\n2. **Auto-tagging**: Gemini analyzes the render and proposes descriptive tags. Each tag is created in VNTANA (or matched if it already exists) and written onto the asset, capped at 10 tags.\n\n### Setup steps\n\n- Install the VNTANA community node, create a VNTANA API credential, and assign it to the **Download renders** and **Upload a render** nodes.\n- Replace `YOUR_WORKSPACE_UUID` in those two nodes and in the **Resolve Tags & Update Asset** code node.\n- Add your org-scoped VNTANA API token in the **Resolve Tags & Update Asset** code node (rotate it periodically).\n- Create a query-auth credential with your Gemini API key and assign it to both Gemini nodes.\n- Register a VNTANA webhook for asset publish events.\n\n### Customization\n\nSwap the style prompt per product category (studio, lifestyle, footwear), edit the tagging prompt, or change the 10-tag cap. Each Gemini call is billed by Google."
},
"typeVersion": 1
},
{
"id": "sticky-note-trigger",
"name": "Note: Trigger",
"type": "n8n-nodes-base.stickyNote",
"position": [
160,
208
],
"parameters": {
"color": 7,
"width": 420,
"height": 190,
"content": "## Trigger and download\n\nFires on asset publish, then downloads the asset's platform renders. Set your Workspace UUID here."
},
"typeVersion": 1
},
{
"id": "sticky-note-skip",
"name": "Note: Skip",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
208
],
"parameters": {
"color": 7,
"width": 400,
"height": 190,
"content": "## Skip guard\n\nFilters out renders that were already processed so republishing an asset never re-bills Gemini."
},
"typeVersion": 1
},
{
"id": "sticky-note-tagging",
"name": "Note: Tagging",
"type": "n8n-nodes-base.stickyNote",
"position": [
1312,
-640
],
"parameters": {
"color": 7,
"width": 460,
"height": 220,
"content": "## Auto-tagging branch\n\nGemini proposes tags for the render. The code node creates or matches each tag in VNTANA and updates the asset (max 10 tags). Set your Workspace UUID and API token inside."
},
"typeVersion": 1
},
{
"id": "sticky-note-rerender",
"name": "Note: Rerender",
"type": "n8n-nodes-base.stickyNote",
"position": [
1600,
176
],
"parameters": {
"color": 7,
"width": 460,
"height": 200,
"content": "## Studio re-render branch\n\nGemini re-renders the image with your style prompt, preserving geometry and framing. The result is uploaded to VNTANA as a new render."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"versionId": "fadb2b06-de0a-49fe-b320-a253cc9cf14a",
"nodeGroups": [],
"connections": {
"Limit": {
"main": [
[
{
"node": "Gemini Tagging",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Download renders",
"type": "main",
"index": 0
}
]
]
},
"Gemini Tagging": {
"main": [
[
{
"node": "Resolve Tags & Update Asset",
"type": "main",
"index": 0
}
]
]
},
"Upload a render": {
"main": [
[]
]
},
"Download renders": {
"main": [
[
{
"node": "Skip Images Already Processed",
"type": "main",
"index": 0
}
]
]
},
"Extract Gemini Image": {
"main": [
[
{
"node": "Upload a render",
"type": "main",
"index": 0
}
]
]
},
"Prep Image for Gemini": {
"main": [
[
{
"node": "Limit",
"type": "main",
"index": 0
},
{
"node": "Gemini Studio Re-render",
"type": "main",
"index": 0
}
]
]
},
"Gemini Studio Re-render": {
"main": [
[
{
"node": "Extract Gemini Image",
"type": "main",
"index": 0
}
]
]
},
"Resolve Tags & Update Asset": {
"main": [
[
{
"node": "Gemini Studio Re-render",
"type": "main",
"index": 0
}
]
]
},
"Skip Images Already Processed": {
"main": [
[
{
"node": "Prep Image for Gemini",
"type": "main",
"index": 0
}
]
]
},
"When clicking \u2018Execute workflow\u2019": {
"main": [
[]
]
}
}
}
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.
httpQueryAuthvntanaApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow receives a VNTANA product publish webhook, downloads the product’s renders, uses Google Gemini to create a studio-style re-render and generate catalog tags, then uploads the new render back to VNTANA and updates the product’s tags via the VNTANA API. Receives a…
Source: https://n8n.io/workflows/17320/ — 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.
legal_rag_telegram_api_current_github_ready. Uses telegramTrigger, httpRequest. Event-driven trigger; 56 nodes.
This n8n workflow automatically generates presentation-style "screen recording" videos with AI-generated slides and a talking head avatar overlay. You provide a topic and intention, and the workflow h
Monitor Google Drive folder, parsing PDF, DOCX and image file into a destination folder, ready for further processing (e.g. RAG ingestion, translation, etc.) Keep processing log in Google Sheet and se
This workflow is designed for individuals and businesses looking to streamline the creation of engaging promotional videos. Whether you're marketing a product or developing a personal brand, this AI-d
Automatically create YouTube short videos using Elevenlabs, Hailuo AI. Uses httpRequest, googleCloudStorage, googleSheets. Event-driven trigger; 38 nodes.