# Tech Stack Switch Alerts - Website Technology Monitor (`tiger19935/tech-stack-switch-alerts`) Actor

Monitor which technologies websites use (CMS, ecommerce, analytics, CRM, chat, payments, CDN) and get alerts when a site adds, removes or switches one, e.g. Magento to Shopify. 200 signatures, state kept between runs.

- **URL**: https://apify.com/tiger19935/tech-stack-switch-alerts.md
- **Developed by:** [Andrew Boiko](https://apify.com/tiger19935) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 site scanneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Tech Stack Switch Alerts - Website Technology Monitor

Find out **when a company changes the technology behind its website**. The actor detects the stack of any site (CMS, ecommerce platform, site builder, analytics, tag manager, ad pixels, CRM and marketing automation, email tools, chat and support, payments, A/B testing, CDN and hosting, JavaScript frameworks, cookie consent) using **200 built-in signatures**, remembers it between runs and raises an alert when something is **added, removed or switched** - for example *Magento -> Shopify*, *Intercom -> Zendesk* or *no CRM -> HubSpot*.

Technology changes are strong buying signals: a company that just moved to Shopify needs apps and agencies, a company that dropped its old analytics may be evaluating alternatives, a site that added Stripe is starting to sell online. Run the monitor weekly on your prospect list and act on the changes.

### Who is it for?

- **Sales teams of SaaS, apps and agencies** - catch prospects the week they adopt or drop a technology you sell or replace.
- **Agencies and consultants** - find sites migrating platforms (replatforming leads).
- **Market researchers** - track adoption of tools across a list of companies.
- **Competitive intelligence** - watch what your competitors add to their sites.

### What you get

Two row types in one dataset (`rowType`):

1. `site-scan` - one per site: technologies found (with confidence and evidence), grouped by category, plus the number of changes since the last run.
2. `alert` - one per change: `added`, `removed` or `switched` with category, from/to, evidence, when it was last seen and when it was detected.

#### Input

| Field | Type | Description |
|---|---|---|
| `domains` | list | Websites to monitor (domains or URLs), up to 2000 per run. |
| `watch` | list | Optional: only alert on these technologies (`Shopify`) or categories (`category:CRM`). |
| `monitorName` | string | Name of the saved state; keep it stable for scheduled runs. |
| `includeBaseline` | boolean | Also output the technologies found on the first scan as `added` alerts (charged per alert). Default off. |
| `includeUnchanged` | boolean | Include the full technology list in site-scan rows of unchanged sites. Default off (counts and grouped names only). |
| `scanExtraPages` | boolean | Scan up to 2 extra internal pages (pricing, checkout, cart, shop ...). Default on. |
| `maxDomains` | integer | Safety limit per run (default 200, max 2000). |
| `includeFailed` | boolean | Add free `error` rows for sites that could not be scanned. |

#### Output example - alert

```json
{
  "rowType": "alert",
  "id": "shop.example.com:switched:Ecommerce platform:Magento>Shopify:2026-10-14",
  "alertType": "switched",
  "domain": "shop.example.com",
  "summary": "shop.example.com switched from Magento to Shopify (Ecommerce platform)",
  "category": "Ecommerce platform",
  "from": "Magento",
  "to": "Shopify",
  "evidence": ["script/resource: cdn.shopify.com/", "inline script: Shopify.theme", "response header: x-shopid: 12345"],
  "confidence": "high",
  "previousSeenAt": "2026-10-07T08:00:11.000Z",
  "detectedAt": "2026-10-14T08:00:09.000Z",
  "isBaseline": false,
  "source": "Direct scan of the public website"
}
```

#### Output example - site scan

```json
{
  "rowType": "site-scan",
  "domain": "shop.example.com",
  "status": "scanned",
  "finalUrl": "https://shop.example.com/",
  "httpStatus": 200,
  "pagesScanned": ["https://shop.example.com/", "https://shop.example.com/pricing"],
  "techCount": 9,
  "technologiesByCategory": { "Ecommerce platform": ["Shopify"], "Analytics": ["Google Analytics"], "CRM & marketing automation": ["Klaviyo"] },
  "changesDetected": 2,
  "alertCount": 2,
  "isBaseline": false,
  "technologies": [{ "name": "Shopify", "category": "Ecommerce platform", "confidence": "high", "score": 100, "evidence": ["..."] }],
  "scannedAt": "2026-10-14T08:00:09.000Z"
}
```

The run also writes an `OUTPUT` record (counts, charged events, skipped sites with reasons, warnings, `stoppedReason`) and a short `RESULTS_MD` summary to the key-value store.

### Pricing

**Pay only for results**, per event:

| Event | Price | When |
|---|---|---|
| `site-scan` | $0.003 | One per site that was scanned successfully with at least one technology detected |
| `switch-alert` | $0.01 | One per alert row (`added`, `removed` or `switched`) |

Nothing is charged for unreachable or blocked sites, robots.txt exclusions, pages where nothing was detected, duplicates, or anything beyond the maximum cost you set for the run. Apify's free plan credit lets you try the actor at no cost; volume discounts (Bronze/Silver/Gold) apply automatically. Example: 500 prospect sites per week with about 15 alerts costs 500 x $0.003 + 15 x $0.01 = $1.65 per run.

### How it works

1. **Fetch** the homepage (and up to 2 internal pages such as pricing or checkout) with an honest User-Agent, low concurrency and `robots.txt` respected.
2. **Detect** technologies by matching 200 signatures against script and resource URLs, stylesheet links, meta tags, HTML comments, inline script globals, cookie names, response headers and DOM markers. Every detection carries evidence and a confidence (`high` / `medium` / `low`).
3. **Compare** with the snapshot saved in a named key-value store (`tech-stack-<hash>`, derived from `monitorName`).
4. **Alert** following these rules:
   - `added`: a technology appears that was not in the previous snapshot (only medium/high confidence).
   - `removed`: a technology must be **missing in two consecutive successful scans** before the alert, so a flaky page or a lazy-loaded script does not cause noise.
   - `switched`: two ways. In the exclusive categories *CMS*, *Ecommerce platform* and *Site builder*, if one technology disappears and another appears in the **same scan**, you get one `switched` alert immediately. In other categories, a confirmed removal that follows an `added` technology of the same category is reported as `switched from A to B`.
5. **Safety**: a failed, blocked or empty fetch never changes the stored snapshot and never creates a removal.

The first scan of a site is a **baseline**: you get the `site-scan` row but no alerts (unless you enable `includeBaseline`).

### Limits and honest caveats

- Detection works on what the server returns to a plain HTTP request. Technologies loaded only after user interaction, behind a login, or injected late by JavaScript can be missed, and some single-page apps expose little in the HTML.
- It cannot see back-end technology that leaves no trace in the response (databases, most server languages, internal tools).
- Signatures are pattern based. A mention of a vendor in the page text is not enough to trigger a detection, but false positives and false negatives are possible; use the `evidence` and `confidence` fields to judge. `low` confidence detections are listed but never raise alerts.
- Sites behind bot protection (Cloudflare challenge, Akamai, DataDome and similar) are reported as `blocked` and are not charged; the actor does not try to bypass them.
- Switch detection depends on the scan interval: a change is seen at the first run after it went live, and removals take two runs to be confirmed.
- Changing the list of domains without a `monitorName` starts a fresh baseline; set a `monitorName` to keep history while editing the list.
- Not a replacement for dedicated services with crawled historical databases: this actor tells you what changed between your own runs.

### Legal and ethical notes

Only publicly served pages are requested (homepage and at most two internal pages per site), at low rates, with an honest User-Agent, respecting `robots.txt`. No logins, no bypassing of protections and no personal data are collected. Technology usage is public information, but check local rules and the terms of the sites you monitor before using the data commercially.

### FAQ

**Which technologies are detected?** 200 signatures across 14 categories: CMS, Ecommerce platform, Site builder, Analytics, Tag manager, Advertising, CRM & marketing automation, Email service, Chat & support, Payments, A/B testing, CDN & hosting, JavaScript framework and Consent management.

**How do I only get alerts for what matters to me?** Use `watch`, for example `["Shopify", "category:CRM", "category:Payments"]`. State is still updated for everything, so you can widen the list later without a new baseline.

**Why no alert on the first run?** The first run is a baseline. Enable `includeBaseline` to get an `added` alert per detected (watched) technology; they are charged as alerts.

**Why was a removal not reported immediately?** To avoid false alarms, a technology must be missing in two consecutive successful scans.

**What does `blocked` mean?** The site answered with a bot-protection or access-denied page. Nothing is charged and the stored state is untouched.

**Can I run it daily?** Yes. Weekly is enough for most prospecting lists; use daily for competitors.

**Is the `confidence` a probability?** No. It is a rule-of-thumb label: `high` means several independent kinds of evidence, `medium` one strong signal, `low` a weak marker only.

### Use it via API

Replace `username` with the actor owner.

```bash
curl -X POST "https://api.apify.com/v2/acts/username~tech-stack-switch-alerts/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains":["allbirds.com","gymshark.com"],"watch":["category:Ecommerce platform"],"monitorName":"ecom-weekly"}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('username/tech-stack-switch-alerts').call({
    domains: ['allbirds.com', 'gymshark.com'],
    monitorName: 'ecom-weekly',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter((i) => i.rowType === 'alert'));
```

```python
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("username/tech-stack-switch-alerts").call(run_input={
    "domains": ["allbirds.com", "gymshark.com"],
    "monitorName": "ecom-weekly",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["rowType"] == "alert":
        print(item["summary"])
```

### Integrations

Schedule the actor with Apify **Schedules** (weekly is typical), then connect the dataset to **Make**, **n8n** or **Zapier** ("Actor run finished" trigger, then "get dataset items") to send `alert` rows to Slack, your CRM or Google Sheets. Webhooks and the Google Sheets integration work too; filter on `rowType = alert` and `alertType = switched`.

# Actor input Schema

## `domains` (type: `array`):

One website per row: a domain (example.com) or a full URL. Up to 2000 sites per run. The homepage and up to 2 internal pages (pricing, checkout, shop ...) are scanned.

## `watch` (type: `array`):

Only alert on these technologies or categories. Use technology names (Shopify, HubSpot, Stripe) or "category:" plus a category name (category:CRM, category:Ecommerce platform, category:Analytics). A switch alert is kept when either the old or the new technology matches. Leave empty to alert on everything. Site-scan rows are always produced for every scanned site.

## `monitorName` (type: `string`):

Name of the saved state. Runs with the same name compare against each other, so keep one stable name per watch list for scheduled runs. Leave empty to derive it from the list of domains (changing the list then starts a fresh baseline).

## `includeBaseline` (type: `boolean`):

Off (default): the first scan of a site only records its stack (you still get the site-scan row). On: the first scan also returns one 'added' alert per detected technology that matches your watch list, and each such alert is charged as a switch-alert event, so combine with a watch list to keep the cost low.

## `includeUnchanged` (type: `boolean`):

Off (default): site-scan rows of sites with no change since the last run contain only counts and technologies grouped by category. On: they also contain the full detection list with evidence. Sites with changes and baseline scans always include it.

## `scanExtraPages` (type: `boolean`):

Also scan up to 2 internal pages found on the homepage (pricing, checkout, cart, shop, signup). Finds payment and e-commerce tools that the homepage does not load. Disable for the fastest, lightest scan.

## `maxDomains` (type: `integer`):

Safety limit on the number of domains processed in one run (1 to 2000). Entries beyond the limit are skipped and listed in the run summary.

## `includeFailed` (type: `boolean`):

Add a free (never charged) 'error' row for sites that could not be scanned (unreachable, blocked by bot protection, robots.txt, nothing detected). Failures are always listed in the run summary (OUTPUT) either way.

## Actor input object example

```json
{
  "domains": [
    "shopify.com",
    "wordpress.org"
  ],
  "watch": [],
  "monitorName": "",
  "includeBaseline": false,
  "includeUnchanged": false,
  "scanExtraPages": true,
  "maxDomains": 200,
  "includeFailed": false
}
```

# Actor output Schema

## `results` (type: `string`):

All result rows.

## `summary` (type: `string`):

Counts, charged events and warnings.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "shopify.com",
        "wordpress.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tiger19935/tech-stack-switch-alerts").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "domains": [
        "shopify.com",
        "wordpress.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tiger19935/tech-stack-switch-alerts").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "shopify.com",
    "wordpress.org"
  ]
}' |
apify call tiger19935/tech-stack-switch-alerts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tiger19935/tech-stack-switch-alerts"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/xu8gybm91fBQ7DaGV/builds/CgEV8fUSWlv0lemGy/openapi.json
