{
  "name": "FLUF + Airtable \u2014 inventory intake and approved publishing",
  "nodes": [
    {
      "id": "3286ee74-28e7-5aa8-b602-2049c615ff78",
      "name": "Setup guide",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -400
      ],
      "parameters": {
        "content": "## Before enabling\nRead https://fluf.io/support/connect-airtable/\n1. Set table IDs in Configure workspace.\n2. Select your Airtable PAT credential on Airtable requests.\n3. For FLUF HTTP requests use Header Auth: Authorization = Bearer YOUR_FLUF_PAT.\n4. Select FLUF credentials on each trigger.\n5. Choose n8n as workflow owner for this base.\n6. Publishing requires allowPublish=true AND Approved for publishing on the row.\nTest with zero-stock drafts first. Use the same currency as your FLUF draft currency.",
        "height": 360,
        "width": 440
      }
    },
    {
      "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": []
}
