# Website Tech Stack Detector: BuiltWith & Wappalyzer Alternative (`megalodomnin/website-tech-stack-detector`) Actor

Bulk tech stack lookup for a list of websites, a live pay-per-result alternative to BuiltWith and Wappalyzer. CMS, ecommerce platform, frameworks, analytics, tag managers, payments, hosting, CDN, email provider. 21 ready columns, CSV or JSON. You pay only for sites where something was found.

- **URL**: https://apify.com/megalodomnin/website-tech-stack-detector.md
- **Developed by:** [Evgeny Domnin](https://apify.com/megalodomnin) (community)
- **Categories:** Developer tools, SEO tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 website with technologies detecteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Detector

Give it a list of websites. Get back what each one runs: CMS, ecommerce platform, JavaScript framework, analytics, tag manager, payment processor, CRM, hosting, CDN, server language, email provider.

**You pay only for websites where something was found.** Blocked, dead and empty rows are still written to the dataset. They cost nothing.

### What you get

- **21 ready columns** per website. Open the CSV, filter by `CMS = Shopify`. No JSON parsing.
- **Proof for every technology.** Which channel matched and what exactly: header, cookie, script, meta tag, JavaScript global, MX record.
- **CPE identifiers** for vulnerability lookups. 463 technologies carry one, mostly CMSes, web servers and web frameworks.
- **Email provider from MX**, in one DNS query. Google Workspace, Microsoft 365, Proofpoint, self-hosted.
- **Versions and confidence**, where the page gives them away.
- **7613 technologies** in the rule set, pinned to a fixed commit.

### What it is for

- Lead lists: find every prospect on Shopify, or every site still on WordPress.
- Competitor research: what stack a market uses.
- Sales signals: who runs HubSpot, Segment, Klaviyo, Stripe.
- Security triage: CPE strings join your rows to a vulnerability database.

### Input

Paste domains, one per line. A bare host name is fine.

```json
{ "urls": ["allbirds.com", "vercel.com", "wordpress.org"] }
```

**Upload a file** instead: `.txt` or `.csv`, one website per line. In a CSV the first column that looks like a domain is used, so a CRM export works as is.

```
shopify.com,Shopify Inc,5000
allbirds.com;Allbirds
wordpress.org
```

A link to a remote list works too: `{"startUrls": [{"requestsFromUrl": "https://example.com/domains.txt"}]}`.

The field can also be called `domains`, `websites`, `targetUrls`, `startUrls` or `url`. Mix them if you like. Duplicates are dropped.

#### Options

| Option | What it does |
|---|---|
| `alwaysBrowser` | Render every site in a browser. **On by default** - it is where analytics, tag managers and pixels come from. Turn it off for a run about three times faster. |
| `useBrowser` | Turn off for the fastest, cheapest pass: one plain HTTP request per site and nothing else. |
| `slim` | Row without the per-technology array. 3.6x lighter. Columns stay, evidence and CPE go. |
| `maxConcurrency` | Parallel requests. Leave empty: the Actor takes 10 per CPU core, and Apify gives one core per 4 GB of run memory. |

### Output

One row per website.

```json
{
  "input": "ledger.com",
  "url": "https://www.ledger.com/",
  "status": "ok",
  "title": "Ledger Crypto Wallet - Security for DeFi & Web3",

  "technologyCount": 16,
  "technologyNames": "WordPress, Contentful, MySQL, PHP, Yoast SEO, Swiper, Optimizely, OneTrust, jQuery Migrate, jQuery, Google Tag Manager, Font Awesome, Cloudflare Bot Management, Cloudflare, cdnjs, Google Workspace",

  "CMS": "WordPress, Contentful, Yoast SEO",
  "Ecommerce": "",
  "Payments": "",
  "Analytics": "Optimizely",
  "Advertising": "",
  "Tag managers": "Google Tag Manager",
  "Marketing": "Optimizely",
  "CRM & support": "",
  "Frontend": "Swiper, jQuery Migrate, jQuery, Font Awesome",
  "Web frameworks": "",
  "Languages": "PHP",
  "Databases": "MySQL",
  "Hosting & CDN": "Cloudflare, cdnjs",
  "Security": "Cloudflare Bot Management",
  "Privacy": "OneTrust",
  "SEO": "Yoast SEO",
  "Media": "",
  "Dev tools": "",
  "Business tools": "",
  "Email hosting": "Google Workspace",
  "Other": "",

  "technologies": [
    {
      "name": "WordPress",
      "version": null,
      "confidence": 100,
      "categories": ["CMS", "Blogs"],
      "website": "https://wordpress.org",
      "cpe": "cpe:2.3:a:wordpress:wordpress:*:*:*:*:*:*:*:*",
      "pricing": ["low", "recurring", "freemium"],
      "saas": true,
      "evidence": [
        { "type": "html", "match": "<link rel='stylesheet' href='https://www.ledger.com/wp-content/" },
        { "type": "scriptSrc", "match": "/wp-includes/" }
      ]
    }
    // ...and 15 more
  ],

  "meta": {
    "method": "http",
    "httpStatus": 200,
    "https": true,
    "server": "cloudflare",
    "poweredBy": null,
    "generator": "WPML ver:4.9.6 stt:5,59,1,4,3,28,29,42,45,2,52,53;",
    "durationMs": 173,
    "error": null,
    "analyzedAt": "2026-09-08T07:01:44.019Z",
    "rulesDate": "2026-09-06"
  }
}
```

#### The 21 columns

`CMS`, `Ecommerce`, `Payments`, `Analytics`, `Advertising`, `Tag managers`, `Marketing`, `CRM & support`, `Frontend`, `Web frameworks`, `Languages`, `Databases`, `Hosting & CDN`, `Security`, `Privacy`, `SEO`, `Media`, `Dev tools`, `Business tools`, `Email hosting`, `Other`.

- Every column is always there. Empty string when nothing was found. Your saved filter keeps working between runs.
- Values are comma-separated strings, not lists. A list would export to CSV as `CMS/0`, `CMS/1`.
- All 109 categories of the rule set map into these 21. A test checks it, so nothing falls out silently.
- One technology can sit in two columns. Yoast SEO is a WordPress plugin and an SEO tool. Exact categories are in `technologies[].categories`.

#### Evidence

Every technology says why we say so.

| `type` | Where it matched |
|---|---|
| `headers` | response header |
| `cookies` | cookie name |
| `meta` | meta tag |
| `scriptSrc`, `scripts` | script URL or inline script |
| `html` | page markup |
| `js` | JavaScript global in the rendered page |
| `mx` | MX record, for the email provider |
| `implied` | not seen directly, implied by another technology. `React` implied by `Next.js`, `MySQL` implied by `WordPress` |

#### Status

| `status` | Meaning | Charged |
|---|---|---|
| `ok` | something real was found | **yes** |
| `infrastructure-only` | the site answered, but only said where it is hosted or what wall guards it | no |
| `blocked` | 4xx or 5xx | no |
| `dead` | DNS failure, refused connection, timeout | no |

Never an answer on its own:

- protocol markers: `HSTS`, `HTTP/2`, `HTTP/3`, `Open Graph`, `RSS`, `PWA`, `Cloudflare`;
- hosting only: PaaS, IaaS, CDN, web servers, reverse proxies, caching, load balancers;
- the wall in front of the site: anti-bot, captcha, cookie banner.

A language, a framework, a CMS, an analytics tag or a payment processor counts, even alone.

### How it works

1. One HTTP request to the home page, sent with a real browser TLS fingerprint. Answers most sites in about a second.
2. Then the page is opened in a real Chromium. JavaScript runs, and the rendered HTML, cookies and JavaScript globals are read. Analytics, tag managers and ad pixels exist only after this step: measured on 200 sites, it is the difference between 8 technologies per site and 12. Turn it off for a run about three times faster.
3. One DNS query for MX, in parallel. Never touches the site.

### Measured, not promised

Run on 2026-09-14 with this Actor: 100 active Y Combinator company sites, 100 sites from the Tranco top-250k on Western commercial TLDs.

| | YC company sites | General web |
|---|---:|---:|
| Answered with technologies (charged) | **89 of 100** | **71 of 100** |
| Only infrastructure (free) | 0 | 12 |
| 4xx or 5xx (free) | 1 | 6 |
| Did not resolve or connect (free) | 10 | 11 |
| Median technologies per answered site | **12** | **10** |
| Email provider found | 70 | 51 |

The misses on the general-web sample are the sample, not the Actor: those domains do not resolve or refuse every connection.

All 200 sites took 8 to 9.5 minutes on the default 4 GB of run memory, across repeated runs.

Speed scales with the memory you give the run, because Apify gives one CPU core per 4 GB: the same 200 sites take about 4.5 minutes at 8 GB and under 3 minutes at 16 GB. Same answers, the run just has more cores to work with.

### What it does not do

- Home page only. A technology used only in checkout or behind a login will not show up.
- Sites that refuse datacenter traffic stay refused. Proxies were measured: of 13 such sites, a residential IP recovered 2. The other 11 refuse everyone.
- No login, no crawling past the first page.

### Compared to BuiltWith and Wappalyzer

Different jobs, and this one is narrower.

- **They answer "which sites in the world run X".** They own a historical database and can sell you a list, with adoption history. This Actor cannot do that at all.
- **This Actor answers "what does each of these sites run right now".** You bring the domain list, it visits every site live, in a real browser, and returns the current answer with the evidence for each finding.
- **Pay per result, $5 per 1000.** No subscription, no monthly floor. A site where nothing was found costs nothing.

If your list of domains already exists, this is the cheaper and fresher route. If you need the database itself, buy the database.

### Detection rules

Open rule set [`enthec/webappanalyzer`](https://github.com/enthec/webappanalyzer), pinned to one commit, so the same page gives the same answer next month.

- Commit `2b5eed3a5eede8b2415fcd0b27c003d625ad88d3`
- Commit date 3 September 2026, vendored into this Actor 2026-09-08
- 7613 technologies, 109 categories

`meta.rulesDate` in every row records which rule set produced it.

### Pricing

**$5 per 1000 websites.** Charged per row with `status: ok`. Nothing else is charged.

A row that says `Cloudflare, HTTP/3` and nothing else tells you nothing about the site. Here it is free.

### Changelog

#### 0.1 - 2026-09-14

- Every site is now rendered in a browser, not only the silent ones. Median technologies per site went from 8 to 12, and no site came back with less.
- The run is a single queue instead of two passes, and HTML parsing moved off the main thread. The plain pass got about twice as fast; the full browser pass on 16 GB now takes what the plain pass used to take on 4 GB.
- A run that is interrupted and resumed no longer repeats sites, so nothing is charged twice.
- Parallelism is derived from the memory you give the run.

#### 0.1 - 2026-09-08

- Email provider from MX records, new `Email hosting` column.
- Every technology carries evidence. Implied ones now say what implied them.
- HTTP pass sends a real browser TLS fingerprint.
- Upload a `.txt` or `.csv` with your domains, or link a remote list.
- `slim` output option.
- 21 category columns, all 109 rule-set categories mapped.
- Anti-bot challenge pages no longer count as an answer, and are no longer charged.

#### 0.1 - 2026-09-06

First release. HTTP first, browser pass for silent sites, rules pinned to a commit, charging only for rows that carry a result.

# Actor input Schema

## `urls` (type: `array`):

Domains or URLs, one per line. A bare host name such as shopify.com is fine, the scheme is added for you.

## `alwaysBrowser` (type: `boolean`):

On by default, and it is where most of the answer comes from: analytics, tag managers and ad pixels only exist after JavaScript runs. Measured on 200 sites: 12 technologies per site with it, 8 without, and not a single site found less. Turn it off and the run is about three times faster, at the cost of the marketing stack.

## `useBrowser` (type: `boolean`):

Turn this off for the fastest, cheapest pass: one plain HTTP request per site and nothing else. Sites that answer only after JavaScript runs will come back thin or empty.

## `slim` (type: `boolean`):

Drop the per-technology breakdown and keep only the columns. About three times smaller rows. Evidence, versions and CPE identifiers are gone.

## `maxConcurrency` (type: `integer`):

How many websites to fetch at once. Leave it empty: the actor takes 10 per CPU core, and Apify gives one core per 4 GB of run memory. A higher number is capped to that and the run log says so. Want 50 at once? Give the run 20 GB of memory.

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

Same as Websites. Accepted so you do not have to guess the field name.

## `websites` (type: `array`):

Same as Websites.

## `targetUrls` (type: `array`):

Same as Websites.

## `startUrls` (type: `array`):

Same as Websites, in the request-list format used by other Actors.

## `url` (type: `string`):

A single website, for a quick one-off check.

## `domainsFile` (type: `array`):

Upload a .txt or .csv with one website per line, or point at a file already in a key-value store. In a CSV the first column that looks like a domain is taken.

## Actor input object example

```json
{
  "urls": [
    "allbirds.com",
    "vercel.com",
    "wordpress.org"
  ],
  "alwaysBrowser": true,
  "useBrowser": true,
  "slim": false
}
```

# Actor output Schema

## `table` (type: `string`):

All 21 category columns, one row per website, ready to open in Excel or Google Sheets. The per-technology breakdown is left out here on purpose: a nested array turns into hundreds of columns in CSV.

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

Every field, including the per-technology breakdown with evidence, versions, CPE identifiers and pricing model.

# 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 = {
    "urls": [
        "allbirds.com",
        "vercel.com",
        "wordpress.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("megalodomnin/website-tech-stack-detector").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 = { "urls": [
        "allbirds.com",
        "vercel.com",
        "wordpress.org",
    ] }

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

```

## MCP server setup

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

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/xKgwUKRhgsaprn1rd/builds/gGscBIEnUht0JNUD6/openapi.json
