{
  "name": "Listingrail: daily new listings and price cuts to Google Sheets and Slack",
  "nodes": [
    {
      "parameters": {
        "content": "## Daily property alerts\n1. Create a **Header Auth** credential: name `Authorization`, value `Bearer re_...` (free key at https://listingrail.com/?utm_source=n8n#key).\n2. Pick it on the HTTP Request node. Change `source`, `location`, `type`, `limit` in its body.\n3. Pick a Google Sheets credential and a spreadsheet with the header row `status, id, source, price, previous_price, currency, beds, baths, address, url, seen_at`.\n4. Slack is off by default. Enable it and choose a channel.\nCost: 1 credit per listing returned, empty runs are free. The first run lists everything as new.\nGuide: https://listingrail.com/guides/n8n-property-alerts/?utm_source=n8n",
        "height": 300,
        "width": 520
      },
      "name": "Setup",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -120
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 7
            }
          ]
        }
      },
      "name": "Every day at 7",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        400,
        100
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://listingrail.com/v1/listings/search?utm_source=n8n",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ source: \"zumper\", location: \"Austin, TX\", type: \"rent\", limit: 50 }) }}",
        "options": {
          "timeout": 300000
        }
      },
      "name": "Search listings",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        620,
        100
      ],
      "credentials": {
        "httpHeaderAuth": {
          "id": "",
          "name": "Listingrail key"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Remember every listing id with its last price in the workflow's static data.\n// Emit a row only for a listing not seen before (\"new\") or one whose price fell (\"price_cut\").\nconst seen = $getWorkflowStaticData('global');\nseen.prices = seen.prices || {};\nconst out = [];\nfor (const home of $input.first().json.listings || []) {\n  const old = seen.prices[home.id];\n  const status = old === undefined ? 'new' : (home.price != null && home.price < old ? 'price_cut' : null);\n  if (home.price != null) seen.prices[home.id] = home.price;\n  if (!status) continue;\n  out.push({ json: {\n    status, id: home.id, source: home.source, price: home.price, previous_price: old ?? '',\n    currency: home.currency, beds: home.beds, baths: home.baths, address: home.address, url: home.url,\n    seen_at: new Date().toISOString(),\n  } });\n}\nreturn out; // nothing new: no items, so Sheets and Slack do not run\n"
      },
      "name": "New or cheaper",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        840,
        100
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {}
        },
        "options": {}
      },
      "name": "Append to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1060,
        100
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "name",
          "value": "#property-alerts"
        },
        "text": "={{ $json.status === 'price_cut' ? 'Price cut' : 'New' }}: {{ $json.address }} {{ $json.currency }} {{ $json.price }} {{ $json.previous_price ? '(was ' + $json.previous_price + ')' : '' }} {{ $json.url }}",
        "otherOptions": {}
      },
      "name": "Slack (optional)",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1060,
        300
      ],
      "disabled": true
    }
  ],
  "connections": {
    "Every day at 7": {
      "main": [
        [
          {
            "node": "Search listings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search listings": {
      "main": [
        [
          {
            "node": "New or cheaper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "New or cheaper": {
      "main": [
        [
          {
            "node": "Append to Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack (optional)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {}
}