# Website Tech Stack Scanner (`lowlanddata/website-tech-scanner`) Actor

Give it a list of domains and get back what each site runs: e-commerce platform, CMS, frameworks, analytics, payments, CDN, hosting and mail stack. Every detection carries its confidence and the evidence that triggered it, because a stack claim without evidence is a guess. No API key, no login.

- **URL**: https://apify.com/lowlanddata/website-tech-scanner.md
- **Developed by:** [Lowland Data](https://apify.com/lowlanddata) (community)
- **Categories:** Developer tools, MCP servers, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.64 / 1,000 domain 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Tech Stack Scanner

Feed it a list of domains (leads, clients, competitors) and get back what each site runs: e-commerce platform, CMS, frameworks, analytics, payments, CDN, hosting, mail stack. One row per domain, and **every detection carries its confidence and the evidence that triggered it**, because a stack claim without evidence is a guess.

**Live health record:** [lowlanddata.com/status/website-tech-scanner](https://lowlanddata.com/status/website-tech-scanner/) - 30-day success rate and daily canary results, failures included. Full input/output reference: [tool page](https://lowlanddata.com/tools/website-tech-scanner/).

Organization-level data only, by design: domains and technologies, never owners, never emails. The tools this replaces sell you the shop owners' contact details alongside the stack: this one never will, and that is the point.

### Quick start (30 seconds)

1. Paste your domains, one per line, bare domains or full URLs both work. The prefill runs as-is.
2. Hit **Start**. A few seconds per domain later:

```json
{
  "domain": "www.allbirds.com",
  "finalUrl": "https://www.allbirds.com/",
  "ecommerce": "Shopify",
  "cms": null,
  "frameworks": null,
  "analytics": "Google Tag Manager",
  "payments": null,
  "cdn": "Cloudflare",
  "hosting": null,
  "mail": null,
  "technologies": [
    { "technology": "Shopify", "confidence": 100, "evidence": ["script cdn.shopify.com/s/..."] },
    { "technology": "Cloudflare", "confidence": 100, "evidence": ["header server: cloudflare"] }
  ],
  "detectedCount": 8,
  "scannedAt": "2026-09-05T14:00:00.000Z"
}
```

Flat columns carry the single best answer per bucket; `technologies` carries everything above your confidence floor, evidence included.

### Who uses this

- **Agencies** qualify a lead list before pitching: which prospects run WooCommerce (your specialty), which sit on a platform worth a migration pitch, which already carry a competitor's tag.
- **Freelancers and MSPs** audit a new client's site in one run instead of view-source archaeology: stack, analytics, CDN, mail provider, one row.
- **SaaS teams** check whether trial signups actually installed the snippet, and what the rest of their stack looks like.
- **Market researchers** answer questions like "what do Belgian webshops actually run" from their own domain lists.

### What it detects

About **5,400 technologies** across e-commerce platforms, CMSs, JavaScript and web frameworks, analytics, payment processors, CDNs, hosting, mail providers, security layers and advertising tags.

That number deserves the arithmetic behind it. The fingerprint corpus lists 7,722 technologies; this actor reads a page over HTTP and never runs a browser, so it can only evaluate the fingerprints expressed as headers, cookies, meta tags, script sources, HTML patterns, URLs and DNS records, 5,390 of them. The remaining 2,332 are defined by JavaScript variables or DOM state that only exist once a page has executed, and no HTTP-only scanner can see them. Anyone quoting the full 7,600 for a fetch-based detector is quoting a catalogue, not a capability.

Mail detection is MX-based: a verification TXT record never claims the mail column, because proving a vendor relationship is not the same as hosting the mailbox.

**Fingerprints:** the corpus is [enthec/webappanalyzer](https://github.com/enthec/webappanalyzer), the community continuation of Wappalyzer's, redistributed verbatim under **GPL-3.0** and loaded as data at runtime. It is re-vendored by hand rather than tracked live, so the version stamp in the run log tells you how current it is.

### How much does it cost?

$4.99 per 1,000 domains scanned, pay-as-you-go: a 500-domain list is about $2.50, once, against the $295 a month the incumbent tools charge for the same question. A domain that cannot be fetched is reported as a failure and never billed as a result.

### Not technical? Let your AI assistant set it up

Paste this to Claude, ChatGPT or Cursor with your list:

```text
I want to know what technology a list of websites runs, using the Apify actor
lowlanddata/website-tech-scanner. Build me the input JSON.

Fields:
- domains: array of domains or URLs (required, up to 10,000)
- includeDns: boolean, adds mail-stack detection via MX/TXT (default true)
- minConfidence: 0-100, drop weaker detections (default 50)
- maxItems: cap on scanned domains (default 1000)

Here is my list: <paste domains>

Give me the JSON only, then tell me how to run it and export the results
to a spreadsheet.
```

### Input

| Field           | Type     | Default | Notes                                                                                                               |
| --------------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------------- |
| `domains`       | string\[] | -       | Bare domains or URLs; normalized to hostnames.                                                                      |
| `includeDns`    | boolean  | `true`  | MX/TXT lookups for the mail stack.                                                                                  |
| `minConfidence` | integer  | `50`    | Confidence floor; kept detections always carry evidence.                                                            |
| `maxItems`      | integer  | `1000`  | Upper bound on scanned domains.                                                                                     |
| `expectations`  | object   | -       | Accuracy-canary mode: fail the run if a domain misses an expected technology. For scheduled QA, not everyday scans. |

### Use it from your code

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/lowlanddata~website-tech-scanner/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"domains":["wordpress.org","www.allbirds.com"]}'
```

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lowlanddata/website-tech-scanner').call({
  domains: ['client-a.be', 'client-b.nl', 'prospect.de'],
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient

client = ApifyClient(token=os.environ["APIFY_TOKEN"])
run = client.actor("lowlanddata/website-tech-scanner").call(run_input={
    "domains": open("domains.txt").read().split(),
    "minConfidence": 60,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["domain"], row["ecommerce"], row["cms"], row["cdn"])
```

A few hundred domains fit inside the 300-second synchronous wall; larger lists run asynchronously.

### Use it with AI agents (MCP)

```bash
claude mcp add apify --transport http "https://mcp.apify.com?actors=lowlanddata/website-tech-scanner"
```

Cursor or Claude Desktop: add a custom connector with `https://mcp.apify.com?actors=lowlanddata/website-tech-scanner`. Sign in with your Apify account when prompted. Runs are billed to it. Details: [Apify MCP docs](https://docs.apify.com/platform/integrations/mcp).

Prompts that work once connected:

- "What do these ten websites run? Flag anything on Magento."
- "Which of my prospect list is on Shopify with Klarna enabled?"
- "Check what stack my competitor's site uses and summarize it."

### Is this legal?

Yes. The scanner performs one polite homepage request per domain (the same request any visitor's browser makes) plus public DNS lookups, and reads what the site itself announces: headers, script URLs, meta tags. Nothing is bypassed, nothing logged in, nothing probed beyond the front door. And the output contains no personal data at all: no names, no emails, no phone numbers: technology facts about organizations' websites.

### Honest limits

- **Headless setups undercount.** A Shopify store rendered through a custom Next.js frontend can read as Next.js only: the homepage does not always betray the commerce engine behind it. Detections are labeled with what the page actually shows.
- **Detection is probabilistic.** That is why every detection ships with confidence and evidence, and why `minConfidence` exists. An empty column means "not detectable from the front door", not "definitely absent".
- **A blocked or unreachable domain** is reported as a per-domain failure (uncharged), not silently skipped.

### Related actors

- [Tech Change Monitor](https://apify.com/lowlanddata/tech-change-monitor), the alerting sibling: remembers each domain's stack and reports only changes. Watching beats re-scanning.

### Troubleshooting

- **"Accuracy check failed: ..."**: you set `expectations` and a domain missed one; this mode exists for scheduled QA and fails loudly by design.
- **A technology you can see is missing**: check `minConfidence`; weak-evidence detections sit below 50 by default. Still missing at 0? Open an issue with the domain: fingerprint gaps are fixable and reports are read daily.
- **Fewer rows than domains**: unreachable domains are reported in the run's summary as failures, never billed as results.

# Actor input Schema

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

Websites to scan - bare domains or full URLs, one per line. Up to 10,000 per run.

## `includeDns` (type: `boolean`):

Adds mail-stack and verification-record detections via public DNS. Cheap; turn off for very large fetch-only scans.

## `minConfidence` (type: `integer`):

Drop detections below this confidence. Every kept detection carries the evidence that triggered it.

## `maxItems` (type: `integer`):

Upper bound on scanned domains (and dataset rows).

## `proxyConfiguration` (type: `object`):

One polite homepage fetch per domain; the default datacenter proxy is plenty.

## `expectations` (type: `object`):

Optional map of domain to required technologies. When set, the run FAILS if any expected technology is missing - built for scheduled accuracy canaries, not everyday scans.

## `reportFailures` (type: `boolean`):

When a run fails, send the developer the actor name and build, an error category, the error message (truncated) and a one-way hash of your input - so a bug can be fixed without anyone asking you to reproduce it. <b>Your input itself, your results and your identity are never sent, and successful runs send nothing at all.</b> Switch this off and the actor behaves exactly the same.

## Actor input object example

```json
{
  "domains": [
    "wordpress.org",
    "www.allbirds.com",
    "apify.com"
  ],
  "includeDns": true,
  "minConfidence": 50,
  "maxItems": 1000,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "reportFailures": true
}
```

# Actor output Schema

## `stacks` (type: `string`):

One row per domain: e-commerce platform, CMS, frameworks, analytics, payments, CDN, hosting and mail stack, each detection carrying its confidence and the evidence that triggered it.

# 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": [
        "wordpress.org",
        "www.allbirds.com",
        "apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lowlanddata/website-tech-scanner").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": [
        "wordpress.org",
        "www.allbirds.com",
        "apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lowlanddata/website-tech-scanner").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": [
    "wordpress.org",
    "www.allbirds.com",
    "apify.com"
  ]
}' |
apify call lowlanddata/website-tech-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lowlanddata/website-tech-scanner"
        }
    }
}
```

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/aejWszwvUR1hf1eJP/builds/8bZTbclvjkhxEyG3F/openapi.json
