{
  "name": "Get daily AutoTrader.ca used car deal alerts in Telegram and Google Sheets with Apify",
  "nodes": [
    {
      "parameters": {
        "content": "## Get daily AutoTrader.ca used car deal alerts in Telegram and Google Sheets\n\nEvery morning this workflow searches AutoTrader.ca for cars that match your budget, then sends the ones priced below the market to Telegram. Every new car also lands in Google Sheets, so you build a price history of your market without lifting a finger.\n\n### Who it's for\nCar buyers, flippers and small dealers in Canada who want new listings before everyone else sees them.\n\n### How it works\n1. **Schedule** runs the workflow every morning at 7:00.\n2. **Set your search** holds your makes, budget, mileage, year, location and deal threshold.\n3. **Apify** runs the [AutoTrader Canada Car Scraper](https://apify.com/fayoussef/autotrader-canada?fpr=youssef) for listings posted in the last day. It returns price, mileage, VIN, seller and AutoTrader's market median price.\n4. **Remove Duplicates** drops cars you already received in earlier runs.\n5. Every new car is saved to your Google Sheet.\n6. Cars at least `minBelowMarketPct` below the market median go to Telegram in one message.\n\n### How to set up\n1. Create a free [Apify account](https://apify.com/?fpr=youssef) and connect it in the **Apify** node (OAuth or API token).\n2. Create an empty Google Sheet and paste its URL in **Set your search**.\n3. Create a Telegram bot with @BotFather, connect it, and paste your chat id in **Set your search**.\n4. Edit the search values, run once, then activate.\n\n### Requirements\n- Apify account (the free plan works)\n- Google Sheets and a Telegram bot\n\n### How to customize\n- Set `dealsOnly` to false to get every new car, not only deals.\n- Leave `postalCode` empty to search all of Canada.\n- Switch the schedule to hourly if you flip cars.",
        "height": 1100,
        "width": 520,
        "color": 1
      },
      "id": "a7c0a001-0000-4000-8000-000000000001",
      "name": "Sticky Note - About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -760,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 1. Set your search\nSeparate several makes or models with commas. Leave a value empty to skip that filter.\n\n`sellerType`: `private`, `dealer` or empty for both.\n`radiusKm`: 10, 20, 50, 100, 150, 200, 250, 300 or 400.\n`dealsOnly` + `minBelowMarketPct`: Telegram only gets cars at least this % under AutoTrader's market median.",
        "height": 640,
        "width": 520,
        "color": 7
      },
      "id": "a7c0a001-0000-4000-8000-000000000002",
      "name": "Sticky Note - Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -200,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 2. Find new cars\nRuns the [AutoTrader Canada Car Scraper](https://apify.com/fayoussef/autotrader-canada?fpr=youssef) on your Apify account for cars listed in the last `listedWithinDays` days.\n\n**Skip cars already sent** remembers every listing URL across runs, so you never see the same car twice.\n\nUsage is billed per car on your Apify account.",
        "height": 640,
        "width": 520,
        "color": 7
      },
      "id": "a7c0a001-0000-4000-8000-000000000003",
      "name": "Sticky Note - Step 2",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        360,
        -240
      ]
    },
    {
      "parameters": {
        "content": "### 3. Save and alert\nEvery new car becomes one row in your Google Sheet (headers are created on the first run).\n\nOnly deals reach Telegram, combined into one message with up to 20 cars. The rest are in the sheet.",
        "height": 640,
        "width": 920,
        "color": 7
      },
      "id": "a7c0a001-0000-4000-8000-000000000004",
      "name": "Sticky Note - Step 3",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        920,
        -240
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 7
            }
          ]
        }
      },
      "id": "a7c0a001-0000-4000-8000-000000000010",
      "name": "Every morning at 7",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -120,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a7c0a001f-0000-4000-8000-000000000001",
              "name": "makes",
              "value": "Toyota, Honda",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000002",
              "name": "models",
              "value": "",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000003",
              "name": "priceTo",
              "value": 25000,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000004",
              "name": "mileageToKm",
              "value": 150000,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000005",
              "name": "modelYearFrom",
              "value": 2016,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000006",
              "name": "postalCode",
              "value": "Toronto, ON",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000007",
              "name": "radiusKm",
              "value": "100",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000008",
              "name": "sellerType",
              "value": "",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000009",
              "name": "listedWithinDays",
              "value": 1,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000010",
              "name": "maxCars",
              "value": 100,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000011",
              "name": "dealsOnly",
              "value": true,
              "type": "boolean"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000012",
              "name": "minBelowMarketPct",
              "value": 5,
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000013",
              "name": "googleSheetUrl",
              "value": "PASTE_YOUR_GOOGLE_SHEET_URL_HERE",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000014",
              "name": "telegramChatId",
              "value": "PASTE_YOUR_TELEGRAM_CHAT_ID_HERE",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "a7c0a001-0000-4000-8000-000000000011",
      "name": "Set your search",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        120,
        120
      ]
    },
    {
      "parameters": {
        "operation": "Run actor and get dataset",
        "actorSource": "store",
        "actorId": {
          "__rl": true,
          "value": "bBoEDhBBfWHh82dce",
          "mode": "id"
        },
        "customBody": "={{ JSON.stringify(Object.fromEntries(Object.entries({\n  start_urls: [],\n  vehicle_type: 'cars',\n  makes: $json.makes.split(',').map(s => s.trim()).filter(Boolean),\n  models: $json.models.split(',').map(s => s.trim()).filter(Boolean),\n  price_to: $json.priceTo || null,\n  mileage_to_km: $json.mileageToKm || null,\n  model_year_from: $json.modelYearFrom || null,\n  postal_code: $json.postalCode.trim() || null,\n  radius_km: $json.postalCode.trim() && $json.radiusKm ? String($json.radiusKm) : null,\n  seller_type: $json.sellerType.trim() ? [$json.sellerType.trim().toLowerCase()] : [],\n  listed_within_days: $json.listedWithinDays || 1,\n  sort_by: 'age',\n  max_items: $json.maxCars\n}).filter(([k, v]) => v !== null && (k === 'start_urls' || !Array.isArray(v) || v.length > 0)))) }}"
      },
      "id": "a7c0a001-0000-4000-8000-000000000012",
      "name": "Find new cars on AutoTrader.ca",
      "type": "@apify/n8n-nodes-apify.apify",
      "typeVersion": 1,
      "position": [
        460,
        120
      ]
    },
    {
      "parameters": {
        "operation": "removeItemsSeenInPreviousExecutions",
        "dedupeValue": "={{ $json.listing_url }}",
        "options": {}
      },
      "id": "a7c0a001-0000-4000-8000-000000000013",
      "name": "Skip cars already sent",
      "type": "n8n-nodes-base.removeDuplicates",
      "typeVersion": 2,
      "position": [
        720,
        120
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "a7c0a001f-0000-4000-8000-000000000001",
              "name": "Year",
              "value": "={{ $json.year }}",
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000002",
              "name": "Make",
              "value": "={{ $json.make }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000003",
              "name": "Model",
              "value": "={{ $json.model }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000004",
              "name": "Trim",
              "value": "={{ $json.trim || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000005",
              "name": "Price (CAD)",
              "value": "={{ $json.price_cad }}",
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000006",
              "name": "Market median (CAD)",
              "value": "={{ $json.market_median_price ?? '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000007",
              "name": "Vs market (%)",
              "value": "={{ $json.price_vs_market_pct ?? '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000008",
              "name": "Mileage (km)",
              "value": "={{ $json.mileage_km }}",
              "type": "number"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000009",
              "name": "Body",
              "value": "={{ $json.body_type || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000010",
              "name": "Transmission",
              "value": "={{ $json.transmission || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000011",
              "name": "Drivetrain",
              "value": "={{ $json.drivetrain || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000012",
              "name": "Fuel",
              "value": "={{ $json.fuel_type || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000013",
              "name": "Seller",
              "value": "={{ $json.seller_name || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000014",
              "name": "Seller type",
              "value": "={{ $json.is_private_seller ? 'Private' : 'Dealer' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000015",
              "name": "Phone",
              "value": "={{ $json.dealer_phone || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000016",
              "name": "City",
              "value": "={{ $json.city || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000017",
              "name": "Province",
              "value": "={{ $json.province || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000018",
              "name": "VIN",
              "value": "={{ $json.vin || '' }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000019",
              "name": "Found on",
              "value": "={{ $now.toISODate() }}",
              "type": "string"
            },
            {
              "id": "a7c0a001f-0000-4000-8000-000000000020",
              "name": "Link",
              "value": "={{ $json.listing_url }}",
              "type": "string"
            }
          ]
        },
        "options": {
          "ignoreConversionErrors": true
        }
      },
      "id": "a7c0a001-0000-4000-8000-000000000014",
      "name": "Format car row",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        980,
        120
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "={{ $('Set your search').first().json.googleSheetUrl }}",
          "mode": "url"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "id"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": [],
          "schema": []
        },
        "options": {}
      },
      "id": "a7c0a001-0000-4000-8000-000000000015",
      "name": "Save every car to Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        1240,
        20
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "a7c0a001c-0000-4000-8000-000000000001",
              "leftValue": "={{ !$('Set your search').first().json.dealsOnly || ($json['Vs market (%)'] !== '' && Number($json['Vs market (%)']) <= -Math.abs($('Set your search').first().json.minBelowMarketPct)) }}",
              "rightValue": "",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "looseTypeValidation": true,
        "options": {}
      },
      "id": "a7c0a001-0000-4000-8000-000000000016",
      "name": "Keep deals only",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2.2,
      "position": [
        1240,
        240
      ]
    },
    {
      "parameters": {
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "items",
        "options": {}
      },
      "id": "a7c0a001-0000-4000-8000-000000000017",
      "name": "Combine into one message",
      "type": "n8n-nodes-base.aggregate",
      "typeVersion": 1,
      "position": [
        1460,
        240
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $('Set your search').first().json.telegramChatId }}",
        "text": "={{ (() => { const esc = s => String(s ?? '').replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');\nconst cars = $json.items;\nconst money = n => '$' + Number(n).toLocaleString('en-US');\nconst lines = cars.slice(0, 20).map(c => {\n  const vs = c['Vs market (%)'] !== '' ? ' (' + (Number(c['Vs market (%)']) > 0 ? '+' : '') + c['Vs market (%)'] + '% vs market)' : '';\n  return '🚗 <b><a href=\"' + c.Link + '\">' + esc([c.Year, c.Make, c.Model, String(c.Trim || '').slice(0, 40)].filter(Boolean).join(' ')) + '</a></b>\\n'\n    + money(c['Price (CAD)']) + esc(vs) + ' · ' + Number(c['Mileage (km)'] || 0).toLocaleString('en-US') + ' km\\n'\n    + esc([c.City, c.Province].filter(Boolean).join(', ')) + ' · ' + esc(c['Seller type']) + (c.Seller ? ': ' + esc(c.Seller) : '');\n});\nreturn '<b>' + cars.length + ' new AutoTrader.ca ' + ($('Set your search').first().json.dealsOnly ? 'deal' : 'car') + (cars.length === 1 ? '' : 's') + '</b>\\n\\n' + lines.join('\\n\\n')\n  + (cars.length > 20 ? '\\n\\n+ ' + (cars.length - 20) + ' more in your Google Sheet' : '');\n})() }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "HTML",
          "disable_web_page_preview": true
        }
      },
      "id": "a7c0a001-0000-4000-8000-000000000018",
      "name": "Send deals to Telegram",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1660,
        240
      ],
      "webhookId": "a7c0a001w-0000-4000-8000-000000000001"
    }
  ],
  "connections": {
    "Every morning at 7": {
      "main": [
        [
          {
            "node": "Set your search",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set your search": {
      "main": [
        [
          {
            "node": "Find new cars on AutoTrader.ca",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find new cars on AutoTrader.ca": {
      "main": [
        [
          {
            "node": "Skip cars already sent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Skip cars already sent": {
      "main": [
        [
          {
            "node": "Format car row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format car row": {
      "main": [
        [
          {
            "node": "Save every car to Google Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Keep deals only",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Keep deals only": {
      "main": [
        [
          {
            "node": "Combine into one message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine into one message": {
      "main": [
        [
          {
            "node": "Send deals to Telegram",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}