{
  "name": "FLUF + Airtable \u2014 inventory intake and approved publishing",
  "nodes": [
    {
      "id": "bf1f15d1-d180-5d3b-b620-1e220f678343",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -660,
        -440
      ],
      "parameters": {
        "content": "## Prepare Airtable inventory as FLUF Connect drafts\n\n### How it works\nEvery 5 minutes this workflow reads inventory rows marked **Ready for FLUF** or **Approved for publishing**, sends each one to FLUF Connect as a draft, and writes the FLUF ID and status back to the row. Items are published to your marketplaces only when `allowPublish` is true **and** the row is **Approved for publishing**.\n\n### Setup\n1. Copy the Airtable template (link below) or use your own base with the same field names.\n2. Open **Configure workspace** and paste your base ID and the Inventory, Sales and Tasks table IDs.\n3. Select your Airtable personal access token credential on every Airtable request node.\n4. On FLUF HTTP request nodes use Header Auth: `Authorization` = `Bearer YOUR_FLUF_PAT`.\n5. Select your FLUF Connect API credential on each FLUF trigger.\n6. In FLUF, choose n8n as the automation owner for this base (one owner per base).\n7. Test with zero-stock drafts first, then activate.\n\n### Requirements\nYour own n8n workspace, an Airtable base and a paid FLUF plan with API write access (not included in the trial).\n\nTemplate base: https://airtable.com/appGvPOBL2b7Lfxwg/shryPFF1iDosEzitJ\nGuide: https://fluf.io/support/connect-airtable/",
        "height": 1000,
        "width": 560,
        "color": 4
      }
    },
    {
      "id": "cae1933a-2ea4-5ffe-b407-170cb04e6189",
      "name": "Note 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -60,
        -260
      ],
      "parameters": {
        "content": "## 1. Schedule and workspace\nRuns every 5 minutes. **Configure workspace** holds your base and table IDs and `allowPublish` (false = drafts only).",
        "height": 420,
        "width": 520,
        "color": 7
      }
    },
    {
      "id": "6352cfa1-9fe2-579e-bd80-64aece532ba0",
      "name": "Note 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        540,
        -260
      ],
      "parameters": {
        "content": "## 2. Read ready rows\nFetches up to 10 Inventory rows whose Stage is Ready for FLUF or Approved for publishing.\nAirtable and FLUF requests make up to three attempts in total, waiting 35 seconds after a rate limit (429) or server error. Other errors stop the run so you can see them in the execution.",
        "height": 620,
        "width": 940,
        "color": 7
      }
    },
    {
      "id": "d3a443fd-ad09-56dd-a13d-597c6fb4bcf9",
      "name": "Note 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1580,
        -260
      ],
      "parameters": {
        "content": "## 3. Save in FLUF Connect\n**Prepare records** turns each row into one item. Each item is sent to FLUF, which creates or updates a draft and only requests publishing when allowed. A failed item is passed on so its row can be flagged.",
        "height": 620,
        "width": 1240,
        "color": 7
      }
    },
    {
      "id": "00c2d77e-8d9e-5365-8f20-2d80787faaed",
      "name": "Note 4",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        2920,
        -260
      ],
      "parameters": {
        "content": "## 4. Write the result back\nUpdates the row: FLUF ID, FLUF status, Stage (Draft in FLUF, Publishing requested or Needs attention), Last error and Last synced. Fix a flagged row and mark it Ready for FLUF again.",
        "height": 620,
        "width": 940,
        "color": 7
      }
    },
    {
      "id": "6400c928-4fe5-5dc0-b80f-6fc97f4e3b80",
      "name": "Check ready inventory",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      }
    },
    {
      "id": "8a4d53f4-dff9-5df9-8ac3-212e52f75dbe",
      "name": "Configure workspace",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        300,
        0
      ],
      "parameters": {
        "jsCode": "// Choose a base and its four FLUF tables. Use standard field names from the guide.\nconst config = { baseId: 'REPLACE_BASE_ID', inventory: 'REPLACE_INVENTORY_TABLE_ID', sales: 'REPLACE_SALES_TABLE_ID', tasks: 'REPLACE_TASKS_TABLE_ID', allowPublish: false };\nif (!/^app[A-Za-z0-9]+$/.test(config.baseId)) throw new Error('Set your Airtable base ID in Configure workspace.');\nfor (const key of ['inventory','sales','tasks']) if (!/^tbl[A-Za-z0-9]+$/.test(config[key])) throw new Error('Set the '+key+' table ID.');\nreturn $input.all().map((item,i)=>({json:{...item.json,config},pairedItem:{item:i}}));"
      }
    },
    {
      "id": "7b4840ab-54b4-5c95-bfeb-a9717de2a679",
      "name": "Request Read ready records",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        840,
        0
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ 'https://api.airtable.com/v0/'+$json.config.baseId+'/'+$json.config.inventory+'?pageSize=10&filterByFormula='+encodeURIComponent('OR({Stage}=\"Ready for FLUF\",{Stage}=\"Approved for publishing\")') }}",
        "authentication": "predefinedCredentialType",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 350
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        },
        "nodeCredentialType": "airtableTokenApi"
      },
      "retryOnFail": false,
      "onError": "continueRegularOutput"
    },
    {
      "id": "80871495-015b-533f-be67-beb71d03743b",
      "name": "Prepare records",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1640,
        0
      ],
      "parameters": {
        "jsCode": "const config=$('Configure workspace').first().json.config; return ($json.records||[]).map(r=>({json:{...r,config},pairedItem:{item:0}}));"
      }
    },
    {
      "id": "6dadcd2b-0871-5769-8ff6-659670f52a39",
      "name": "Request Save or publish in FLUF",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2180,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://fluf.io/wp-json/fc/external/v1/n8n/airtable/intake",
        "authentication": "genericCredentialType",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 350
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        },
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {base_id:$json.config.baseId,record_id:$json.id,fields:$json.fields,allow_publish:$json.config.allowPublish} }}"
      },
      "retryOnFail": false,
      "onError": "continueRegularOutput"
    },
    {
      "id": "f10076fa-9925-566c-bee2-d5160f54506d",
      "name": "Request Update preparation status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3220,
        0
      ],
      "parameters": {
        "method": "PATCH",
        "url": "={{ 'https://api.airtable.com/v0/'+$('Prepare records').item.json.config.baseId+'/'+$('Prepare records').item.json.config.inventory+'/'+$('Prepare records').item.json.id }}",
        "authentication": "predefinedCredentialType",
        "options": {
          "batching": {
            "batch": {
              "batchSize": 1,
              "batchInterval": 350
            }
          },
          "response": {
            "response": {
              "fullResponse": true,
              "neverError": true
            }
          }
        },
        "nodeCredentialType": "airtableTokenApi",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ {fields:{'FLUF ID':String($json.id||$('Prepare records').item.json.fields['FLUF ID']||''),'FLUF status':$json.status||'Needs attention','Stage':$json.status==='draft'?'Draft in FLUF':$json.status==='queued'?'Publishing requested':'Needs attention','Last error':$json.error?'Could not prepare this item. Check the n8n execution, correct the item and mark it Ready for FLUF again.':'','Last synced':new Date().toISOString(),'Listing results':JSON.stringify($json.detail||{})} } }}"
      },
      "retryOnFail": false,
      "onError": "continueRegularOutput"
    },
    {
      "id": "80f8a311-2b6e-5bdd-b7a6-26b7a9e2235b",
      "name": "Attempt Read ready records",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        600,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>({json:{...item.json,__airtableAttempt:Number(item.json.__airtableAttempt||0)+1},pairedItem:{item:i}}));"
      }
    },
    {
      "id": "8850dfb6-f123-575b-917a-84f39a628d15",
      "name": "Read ready records",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1080,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>{\n const before=$(\"Attempt Read ready records\").itemMatching(i).json, response=item.json;\n const status=Number(response.statusCode||0);\n if(status>=200 && status<300) return {json:{...response.body,__airtableRetry:false},pairedItem:{item:i}};\n if((status===0||status===429||status>=500) && before.__airtableAttempt<3) return {json:{...before,__airtableRetry:true},pairedItem:{item:i}};\n const error=response.body?.message||response.body?.error?.message||'The request failed. Check the connection, field mapping and service allowance.';\n if(!false) throw new Error(String(error));\n return {json:{error:String(error),__airtableRetry:false},pairedItem:{item:i}};\n});"
      }
    },
    {
      "id": "9ed26b45-e424-5aba-9cdc-e71c271f95a8",
      "name": "Retry Read ready records",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1320,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "retry",
              "leftValue": "={{ $json.__airtableRetry }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "c24cfe2c-997a-5d95-9f47-dd9c282b9391",
      "name": "Wait for Read ready records",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1320,
        200
      ],
      "parameters": {
        "amount": 35,
        "unit": "seconds"
      },
      "webhookId": "e013eb54-426b-5a9a-bb0d-6eb58e8b8851"
    },
    {
      "id": "5421b395-d97c-5b2b-9485-46aa7678c49a",
      "name": "Attempt Save or publish in FLUF",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1940,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>({json:{...item.json,__airtableAttempt:Number(item.json.__airtableAttempt||0)+1},pairedItem:{item:i}}));"
      }
    },
    {
      "id": "9b4f57fb-ff88-5a72-a46e-45ac558be652",
      "name": "Save or publish in FLUF",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2420,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>{\n const before=$(\"Attempt Save or publish in FLUF\").itemMatching(i).json, response=item.json;\n const status=Number(response.statusCode||0);\n if(status>=200 && status<300) return {json:{...response.body,__airtableRetry:false},pairedItem:{item:i}};\n if((status===0||status===429||status>=500) && before.__airtableAttempt<3) return {json:{...before,__airtableRetry:true},pairedItem:{item:i}};\n const error=response.body?.message||response.body?.error?.message||'The request failed. Check the connection, field mapping and service allowance.';\n if(!true) throw new Error(String(error));\n return {json:{error:String(error),__airtableRetry:false},pairedItem:{item:i}};\n});"
      }
    },
    {
      "id": "ea66b269-2587-5112-a0ac-c4465d9190fc",
      "name": "Retry Save or publish in FLUF",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2660,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "retry",
              "leftValue": "={{ $json.__airtableRetry }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "3291793c-3dff-5889-9807-b3ef0c0064e6",
      "name": "Wait for Save or publish in FLUF",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2660,
        200
      ],
      "parameters": {
        "amount": 35,
        "unit": "seconds"
      },
      "webhookId": "dc1b805e-999d-566d-a6f1-461c60276d5c"
    },
    {
      "id": "015b8157-cc80-5a53-ad22-7e7649379efb",
      "name": "Attempt Update preparation status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2980,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>({json:{...item.json,__airtableAttempt:Number(item.json.__airtableAttempt||0)+1},pairedItem:{item:i}}));"
      }
    },
    {
      "id": "74acceec-9b08-5259-85f0-276f4063e43a",
      "name": "Update preparation status",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3460,
        0
      ],
      "parameters": {
        "jsCode": "return $input.all().map((item,i)=>{\n const before=$(\"Attempt Update preparation status\").itemMatching(i).json, response=item.json;\n const status=Number(response.statusCode||0);\n if(status>=200 && status<300) return {json:{...response.body,__airtableRetry:false},pairedItem:{item:i}};\n if((status===0||status===429||status>=500) && before.__airtableAttempt<3) return {json:{...before,__airtableRetry:true},pairedItem:{item:i}};\n const error=response.body?.message||response.body?.error?.message||'The request failed. Check the connection, field mapping and service allowance.';\n if(!false) throw new Error(String(error));\n return {json:{error:String(error),__airtableRetry:false},pairedItem:{item:i}};\n});"
      }
    },
    {
      "id": "3816503f-0a94-5589-a06d-3a25f53a0cd0",
      "name": "Retry Update preparation status",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3700,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "retry",
              "leftValue": "={{ $json.__airtableRetry }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      }
    },
    {
      "id": "b1bdaf52-b89e-5339-89b8-ac4cb5122850",
      "name": "Wait for Update preparation status",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        3700,
        200
      ],
      "parameters": {
        "amount": 35,
        "unit": "seconds"
      },
      "webhookId": "babaf15d-e8fc-5618-b4bd-ac00b339321a"
    }
  ],
  "connections": {
    "Check ready inventory": {
      "main": [
        [
          {
            "node": "Configure workspace",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure workspace": {
      "main": [
        [
          {
            "node": "Attempt Read ready records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare records": {
      "main": [
        [
          {
            "node": "Attempt Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attempt Read ready records": {
      "main": [
        [
          {
            "node": "Request Read ready records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Read ready records": {
      "main": [
        [
          {
            "node": "Read ready records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read ready records": {
      "main": [
        [
          {
            "node": "Retry Read ready records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Read ready records": {
      "main": [
        [
          {
            "node": "Attempt Read ready records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attempt Save or publish in FLUF": {
      "main": [
        [
          {
            "node": "Request Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Save or publish in FLUF": {
      "main": [
        [
          {
            "node": "Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save or publish in FLUF": {
      "main": [
        [
          {
            "node": "Retry Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Save or publish in FLUF": {
      "main": [
        [
          {
            "node": "Attempt Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Attempt Update preparation status": {
      "main": [
        [
          {
            "node": "Request Update preparation status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Update preparation status": {
      "main": [
        [
          {
            "node": "Update preparation status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update preparation status": {
      "main": [
        [
          {
            "node": "Retry Update preparation status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait for Update preparation status": {
      "main": [
        [
          {
            "node": "Attempt Update preparation status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Read ready records": {
      "main": [
        [
          {
            "node": "Wait for Read ready records",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare records",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Save or publish in FLUF": {
      "main": [
        [
          {
            "node": "Wait for Save or publish in FLUF",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Attempt Update preparation status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Retry Update preparation status": {
      "main": [
        [
          {
            "node": "Wait for Update preparation status",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "executionTimeout": 300
  },
  "tags": []
}
