{
  "name": "Monitor Amazon Buy Box, price and stock for your ASINs with Apify, Google Sheets and Slack",
  "nodes": [
    {
      "parameters": {
        "content": "## Catch Amazon Buy Box and listing problems early\n\n### How it works\nEvery morning this workflow checks your own Amazon listings, on one or more marketplaces, with the [Amazon Multi-Domain Product Price & Detail Scraper](https://apify.com/nimbryxo-studio/amazon-multi-domain-product-price-and-detail) Actor on Apify. For each ASIN it reads who holds the Buy Box, the Buy Box price, stock, rating and offer count, and compares them with the row saved in Google Sheets last time.\n\nSlack only hears about changes: another seller wins the Buy Box, the price drops under your minimum, the item goes out of stock, the rating slips under your floor, or the listing stops loading. A problem is reported once when it appears and once when it clears.\n\nIt's for brand owners and sellers who still check their listings by hand.\n\n### Setup\n1. In Apify Console > Settings > API & Integrations, copy your token. In n8n, create a **Header Auth** credential (name `Authorization`, value `Bearer <token>`) and select it in the three Apify nodes.\n2. Add a Google Sheets tab named `Listing health` with this header row: `key, asin, marketplace, title, price, currency, buyBoxSeller, offers, inStock, rating, reviews, url, openIssues, lastChecked`\n3. Connect Google Sheets and Slack.\n4. Fill in **Config**: ASINs, marketplaces, your seller names as Amazon shows them, minimum prices, sheet ID and channel.\n\n### Customization\nAdd marketplaces, set `postalCode` (the Buy Box can differ by delivery area), or turn off `alertWhenResolved`. Each run is billed to your Apify account; `maxCostUsd` caps it.",
        "height": 798,
        "width": 560
      },
      "id": "dd79c3c1-57b1-46e6-afbd-cd3624660847",
      "name": "Overview",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -660,
        -378
      ]
    },
    {
      "parameters": {
        "content": "## 1. Settings and last state\nChecks Config before anything costs money, then reads the row saved for each listing last time.",
        "height": 410,
        "width": 940,
        "color": 7
      },
      "id": "e9e179af-af5a-4147-aee8-effe8ef7ec08",
      "name": "Section: settings",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -210
      ]
    },
    {
      "parameters": {
        "content": "## 2. Run the Actor and wait\nStarts the Apify run and checks its status every minute until it ends. A failed or stuck run goes to the alert below.",
        "height": 850,
        "width": 460,
        "color": 7
      },
      "id": "3a724c92-0b46-444a-a513-8def2c4837bc",
      "name": "Section: run",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        920,
        -210
      ]
    },
    {
      "parameters": {
        "content": "## 3. Find what changed\nFlags Buy Box losses, prices under your minimum, stock-outs, low ratings and listings that stop loading. Each problem is reported when it appears and when it clears.",
        "height": 410,
        "width": 460,
        "color": 7
      },
      "id": "1c14e765-b719-49f9-a7eb-62c6f33085eb",
      "name": "Section: compare",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1400,
        -210
      ]
    },
    {
      "parameters": {
        "content": "## 4. Save and tell Slack\nUpdates one row per listing (matched on `key`) and posts to Slack only when something changed.",
        "height": 850,
        "width": 460,
        "color": 7
      },
      "id": "f6367df8-bf7c-41bc-ab3f-6ed88895f72e",
      "name": "Section: deliver",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1880,
        -210
      ]
    },
    {
      "parameters": {
        "content": "## If the run fails\nPosts the run status and a console link, then stops with an error so the execution shows as failed.",
        "height": 366,
        "width": 460,
        "color": 7
      },
      "id": "141085a7-bc65-4b12-a370-a57298636bcc",
      "name": "Section: failure",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1400,
        274
      ]
    },
    {
      "parameters": {
        "content": "## Paid run\nStarts a run on your Apify account. `maxCostUsd` in Config caps what one run can spend.",
        "height": 148,
        "width": 300,
        "color": 3
      },
      "id": "54707996-06e5-4687-a3cd-1715efdd1c6e",
      "name": "Warning: paid run",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        860,
        -378
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "f9661886-f079-4111-a95b-d43c352f294c",
      "name": "Every day at 07:00",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "{\n  \"asins\": [\n    \"B0DC11YH4J\",\n    \"B08YNDYG71\"\n  ],\n  \"marketplaces\": [\n    \"amazon.com\"\n  ],\n  \"yourSellerNames\": [\n    \"Amazon.com\"\n  ],\n  \"minimumPrices\": {\n    \"B0DC11YH4J\": 39.99\n  },\n  \"ratingFloor\": 4.2,\n  \"priceMoveAlertPct\": 10,\n  \"alertWhenResolved\": true,\n  \"postalCode\": \"\",\n  \"googleSheetId\": \"YOUR_GOOGLE_SHEET_ID\",\n  \"sheetTab\": \"Listing health\",\n  \"slackChannel\": \"#amazon-listings\",\n  \"maxCostUsd\": 1,\n  \"maxWaitMinutes\": 20\n}\n",
        "options": {}
      },
      "id": "540144c5-0e2d-42fe-ab4e-414851d72a91",
      "name": "Config",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        240,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Checks Config before any paid Apify run and builds the Actor input.\nconst cfg = $('Config').first().json;\nconst problems = [];\nconst asins = [...new Set((cfg.asins || []).map((a) => String(a).trim().toUpperCase()).filter(Boolean))];\nif (!asins.length) problems.push('add at least one ASIN to \"asins\"');\nconst badAsins = asins.filter((a) => !/^[A-Z0-9]{10}$/.test(a));\nif (badAsins.length) problems.push(`not 10-character ASINs: ${badAsins.join(', ')}`);\nconst markets = [...new Set((cfg.marketplaces || [])\n  .map((m) => String(m).trim().toLowerCase().replace(/^https?:\\/\\//, '').replace(/^www\\./, '').replace(/\\/.*$/, ''))\n  .filter(Boolean))];\nif (!markets.length) problems.push('add at least one marketplace to \"marketplaces\", for example \"amazon.com\"');\nconst badMarkets = markets.filter((m) => !/^amazon\\.[a-z.]+$/.test(m));\nif (badMarkets.length) problems.push(`not Amazon marketplaces: ${badMarkets.join(', ')}`);\nif (!cfg.googleSheetId || /YOUR_/i.test(String(cfg.googleSheetId))) problems.push('set \"googleSheetId\" to the ID in your sheet URL');\nif (!cfg.slackChannel) problems.push('set \"slackChannel\"');\nif (problems.length) throw new Error(`Config: ${problems.join('; ')}`);\n\nconst actorInput = {\n  asins,\n  marketplace: markets[0],\n  includeDetails: true,\n  includeVariants: false,\n  includeTopReviews: false,\n  includeFailed: true,\n};\nif (markets.length > 1) actorInput.domains = markets;\nif (cfg.postalCode) actorInput.postalCode = String(cfg.postalCode);\n// One listing = one ASIN on one marketplace.\nconst expected = markets.flatMap((m) => asins.map((a) => `${a}@${m}`));\nreturn [{ json: { actorInput, expected } }];\n"
      },
      "id": "20f92b5c-8f2e-4a1b-a009-bd67339b9007",
      "name": "Check settings",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ]
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Config').first().json.googleSheetId }}"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Config').first().json.sheetTab }}"
        },
        "options": {}
      },
      "id": "c0b56b49-9154-47fc-af1e-9db455935bc3",
      "name": "Read previous run from Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        720,
        0
      ],
      "executeOnce": true,
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/acts/nimbryxo-studio~amazon-multi-domain-product-price-and-detail/runs",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            },
            {
              "name": "maxTotalChargeUsd",
              "value": "={{ $('Config').first().json.maxCostUsd }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($('Check settings').first().json.actorInput) }}",
        "options": {
          "timeout": 90000
        }
      },
      "id": "241e6ed9-2d34-4dcd-adbd-f2427744753c",
      "name": "Start Apify run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        0
      ],
      "executeOnce": true
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "succeeded",
                    "leftValue": "={{ $json.data.status }}",
                    "rightValue": "SUCCEEDED",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Succeeded"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "running",
                    "leftValue": "={{ ['READY', 'RUNNING'].includes($json.data.status) && $runIndex < $('Config').first().json.maxWaitMinutes }}",
                    "rightValue": "",
                    "operator": {
                      "type": "boolean",
                      "operation": "true",
                      "singleValue": true
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "Still running"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra",
          "renameFallbackOutput": "Failed or stuck"
        }
      },
      "id": "436a8c85-aeaa-4239-aa96-0d43ac901e4a",
      "name": "Route by run status",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1200,
        0
      ]
    },
    {
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/actor-runs/' + $json.data.id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "waitForFinish",
              "value": "60"
            }
          ]
        },
        "options": {
          "timeout": 90000
        }
      },
      "id": "b98e86b2-13d5-4c58-a574-0b34867f2877",
      "name": "Check run status",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        440
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "url": "={{ 'https://api.apify.com/v2/datasets/' + $json.data.defaultDatasetId + '/items' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "clean",
              "value": "true"
            },
            {
              "name": "format",
              "value": "json"
            }
          ]
        },
        "options": {
          "timeout": 120000
        }
      },
      "id": "6ea55c78-8d29-4034-a23a-12c3ee6e0200",
      "name": "Download results",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1440,
        0
      ],
      "alwaysOutputData": true,
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000
    },
    {
      "parameters": {
        "jsCode": "// Compares today's listings with the row saved for each one last time. A problem is reported once, when it\n// appears, and once more when it clears; quiet days produce no message. Returns ONE item:\n// { rows } for the sheet and { report, message } for Slack.\nconst cfg = $('Config').first().json;\nconst settings = $('Check settings').first().json;\nconst now = new Date().toISOString();\nconst norm = (s) => String(s ?? '').trim().toLowerCase();\nconst num = (v) => {\n  if (v === null || v === undefined || v === '') return null;\n  if (typeof v === 'object') return num(v.value);\n  const n = Number(String(v).replace(/[^0-9.-]/g, ''));\n  return Number.isFinite(n) ? n : null;\n};\nconst money = (v, cur) => (v === null ? 'n/a' : `${cur ? `${cur} ` : ''}${v.toFixed(2)}`);\nconst yours = (cfg.yourSellerNames || []).map(norm).filter(Boolean);\nconst minimums = {};\nfor (const [asin, price] of Object.entries(cfg.minimumPrices || {})) minimums[String(asin).toUpperCase()] = num(price);\nconst ratingFloor = num(cfg.ratingFloor);\nconst movePct = num(cfg.priceMoveAlertPct) ?? 10;\nconst CLEARED = {\n  missing: 'the listing loads again',\n  'no-offer': 'back in stock with a Buy Box offer',\n  buybox: 'the Buy Box is back with your seller',\n  'below-min': 'price is back at or above your minimum',\n  rating: 'rating is back at or above your floor',\n};\n\nconst saved = new Map();\nfor (const item of $('Read previous run from Google Sheets').all()) {\n  const r = item.json || {};\n  if (r.key) saved.set(String(r.key), r);\n}\n\nconst found = new Map();\nfor (const item of $input.all()) {\n  const r = item.json || {};\n  const asin = String(r.asin || (String(r.url || '').match(/\\/dp\\/([A-Z0-9]{10})/) || [])[1] || '').toUpperCase();\n  if (!asin) continue;\n  const market = norm(r.marketplace || (String(r.url || '').match(/(amazon\\.[a-z.]+)/) || [])[1] || settings.actorInput.marketplace);\n  found.set(`${asin}@${market}`, r);\n}\n\nconst short = (t) => (t.length <= 60 ? t : `${t.slice(0, 60).replace(/[\\s,;:–-]+\\S*$/, '')}…`);\nconst label = (row) => `<${row.url}|${row.asin}> on ${row.marketplace}${row.title ? ` (${short(String(row.title))})` : ''}`;\nconst rows = [];\nconst lines = []; // one line per listing that changed: [icon, text]\nlet openedCount = 0;\nlet clearedCount = 0;\nfor (const key of settings.expected) {\n  const [asin, market] = key.split('@');\n  const r = found.get(key);\n  const before = saved.get(key);\n  const issues = [];\n  let row;\n  if (!r || norm(r.recordType) === 'error' || norm(r.status) === 'error') {\n    const why = r && (r.reason || r.error || r.httpStatus);\n    issues.push({ code: 'missing', text: `the listing did not load${why ? ` (${why})` : ''}` });\n    row = { ...(before || {}), key, asin, marketplace: market, url: (before && before.url) || `https://www.${market}/dp/${asin}` };\n  } else {\n    const price = num(r.price);\n    const cur = (r.price && r.price.currency) || r.currency || '';\n    const buyBox = (r.buybox && (r.buybox.soldBy || r.buybox.seller)) || '';\n    const stars = num(r.stars);\n    const min = minimums[asin] ?? null;\n    if (r.inStock === false || price === null) issues.push({ code: 'no-offer', text: 'out of stock or no Buy Box offer' });\n    if (buyBox && yours.length && !yours.includes(norm(buyBox))) issues.push({ code: 'buybox', text: `the Buy Box is held by ${buyBox}` });\n    if (min !== null && price !== null && price < min) issues.push({ code: 'below-min', text: `the Buy Box price ${money(price, cur)} is under your minimum ${money(min, cur)}` });\n    if (ratingFloor !== null && stars !== null && stars < ratingFloor) issues.push({ code: 'rating', text: `the rating fell to ${stars} (your floor is ${ratingFloor})` });\n    row = {\n      key, asin, marketplace: market,\n      title: String(r.title || '').replace(/\\s+/g, ' ').trim().slice(0, 150),\n      price: price ?? '', currency: cur, buyBoxSeller: buyBox,\n      offers: num(r.offerCount) ?? '', inStock: r.inStock === false ? 'no' : 'yes',\n      rating: stars ?? '', reviews: num(r.reviewsCount) ?? '',\n      url: r.url || `https://www.${market}/dp/${asin}`,\n    };\n    const was = before ? num(before.price) : null;\n    if (was && price !== null && Math.abs(price - was) / was * 100 >= movePct) row.move = `price ${money(was, cur)} -> ${money(price, cur)}`;\n  }\n  const codesNow = issues.map((i) => i.code);\n  const codesBefore = String((before && before.openIssues) || '').split(',').map((s) => s.trim()).filter(Boolean);\n  const fresh = issues.filter((i) => !codesBefore.includes(i.code));\n  const gone = cfg.alertWhenResolved === false ? [] : codesBefore.filter((c) => !codesNow.includes(c));\n  const { move, ...clean } = row;\n  const parts = [...fresh.map((i) => i.text), ...gone.map((c) => CLEARED[c] || c)];\n  if (move && !fresh.some((i) => i.code === 'below-min')) parts.push(move);\n  if (parts.length) lines.push([fresh.length ? ':red_circle:' : gone.length ? ':white_check_mark:' : ':arrow_right:', `${label(clean)}: ${parts.join('; ')}`]);\n  openedCount += fresh.length;\n  clearedCount += gone.length;\n  rows.push({ ...clean, openIssues: codesNow.join(','), lastChecked: now });\n}\n\nconst firstRun = saved.size === 0;\nconst total = settings.expected.length;\nconst ORDER = [':red_circle:', ':white_check_mark:', ':arrow_right:'];\nlines.sort((a, b) => ORDER.indexOf(a[0]) - ORDER.indexOf(b[0]));\nconst head = firstRun\n  ? `*Amazon listing check is set up.* Saved a baseline for ${total} listing${total === 1 ? '' : 's'}; from now on you only hear about changes.`\n  : `*Amazon listing check* (${total} listing${total === 1 ? '' : 's'}): ${openedCount} new problem${openedCount === 1 ? '' : 's'}, ${clearedCount} cleared`;\nconst out = [head, ...lines.map(([icon, text]) => `${icon} ${text}`)];\nif (found.size === 0) out.push('_The Apify run returned no rows. If this repeats, check the ASINs and marketplaces in Config._');\n\nconst report = firstRun || lines.length > 0;\nreturn [{ json: { report, message: out.join('\\n'), rows, opened: openedCount, cleared: clearedCount } }];\n"
      },
      "id": "c5ad72eb-4154-4776-a6b4-71d42be14c47",
      "name": "Check every listing for problems",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// One item per sheet row (none on a day without rows, so the sheet step is skipped).\nreturn ($json.rows || []).map((row) => ({ json: row }));"
      },
      "id": "ee4f6f18-ca09-409b-a424-7e5b4a7fcf8a",
      "name": "Rows for the sheet",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1920,
        0
      ]
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Config').first().json.googleSheetId }}"
        },
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Config').first().json.sheetTab }}"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [
            "key"
          ],
          "schema": []
        },
        "options": {
          "cellFormat": "RAW"
        }
      },
      "id": "911e2872-d379-42ce-a257-bf6d2edd2b63",
      "name": "Save to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        2160,
        0
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "report",
              "leftValue": "={{ $json.report }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "9d0f3507-a329-40f2-af64-657c5ea31843",
      "name": "Anything to report?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1920,
        440
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Config').first().json.slackChannel }}"
        },
        "text": "={{ $json.message }}",
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "id": "a72e1656-1110-4e7a-a545-e06767350941",
      "name": "Post the report to Slack",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        2160,
        440
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Config').first().json.slackChannel }}"
        },
        "text": "={{ ':warning: *Monitor Amazon Buy Box, price and stock for your ASINs*: the Apify run ended as *' + $json.data.status + '*' + (['READY','RUNNING'].includes($json.data.status) ? ' (still running after ' + $('Config').first().json.maxWaitMinutes + ' minutes)' : '') + '. Run: https://console.apify.com/view/runs/' + $json.data.id }}",
        "otherOptions": {
          "mrkdwn": true
        }
      },
      "id": "e2d18144-4c3f-48d3-a16b-1d00862e68b4",
      "name": "Alert: run failed",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1440,
        440
      ],
      "retryOnFail": true,
      "maxTries": 2,
      "waitBetweenTries": 3000
    },
    {
      "parameters": {
        "errorMessage": "={{ 'Apify run ' + $('Route by run status').item.json.data.status + ': https://console.apify.com/view/runs/' + $('Route by run status').item.json.data.id }}"
      },
      "id": "56ab7fea-f781-4934-a197-28f141eed0f4",
      "name": "Stop: run failed",
      "type": "n8n-nodes-base.stopAndError",
      "typeVersion": 1,
      "position": [
        1680,
        440
      ]
    }
  ],
  "connections": {
    "Every day at 07:00": {
      "main": [
        [
          {
            "node": "Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Config": {
      "main": [
        [
          {
            "node": "Check settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check settings": {
      "main": [
        [
          {
            "node": "Read previous run from Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read previous run from Google Sheets": {
      "main": [
        [
          {
            "node": "Start Apify run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Apify run": {
      "main": [
        [
          {
            "node": "Route by run status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by run status": {
      "main": [
        [
          {
            "node": "Download results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check run status",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Alert: run failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check run status": {
      "main": [
        [
          {
            "node": "Route by run status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download results": {
      "main": [
        [
          {
            "node": "Check every listing for problems",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check every listing for problems": {
      "main": [
        [
          {
            "node": "Rows for the sheet",
            "type": "main",
            "index": 0
          },
          {
            "node": "Anything to report?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Rows for the sheet": {
      "main": [
        [
          {
            "node": "Save to Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Anything to report?": {
      "main": [
        [
          {
            "node": "Post the report to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert: run failed": {
      "main": [
        [
          {
            "node": "Stop: run failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
