# Tech Stack Detector — BuiltWith & Wappalyzer Alternative (`plainfetch/tech-stack-detector`) Actor

Find out what any website is built with. Bulk-detect CMS, ecommerce platform, analytics, payments, CDN and 7,000+ technologies from a list of URLs. You only pay for URLs where a technology is detected.

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

## Pricing

$5.00 / 1,000 website analyzeds

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?

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

Paste a list of websites, get back what each one is built with: CMS, ecommerce platform, analytics,
payment processor, CDN, frameworks and 7,000+ other technologies — and you only pay for the sites
where something is actually found. **Tech Stack Detector** is a
[BuiltWith](https://builtwith.com) and [Wappalyzer](https://www.wappalyzer.com) **API alternative**
built for bulk analysis: give it a list of URLs and get one dataset item back per site — ready to
filter, sort, or feed into another tool.

### What can you do with it?

- **Enrich lead lists.** Upload a list of prospect domains and segment them by stack — for example,
  "all Shopify stores that also use Klaviyo" — then route each account to the right sales play.
- **Competitive analysis.** Check what your competitors' sites actually run: their CMS, their
  analytics stack, their payment processor, their CDN.
- **Market research for a technology.** Confirm and quantify adoption of a specific technology
  across a list of sites — how many use a given CMS, tag manager, or payment gateway.
- **Feed AI agents.** Call this Actor from the Apify API, an integration, or an MCP-connected AI
  agent so it can look up a site's tech stack as one step in a larger workflow.

### What data do you get?

Each input URL produces one dataset item:

| Field          | Type            | Description                                                                |
| -------------- | --------------- | -------------------------------------------------------------------------- |
| `url`          | string          | The URL you submitted, normalized (scheme added if you left it out).       |
| `finalUrl`     | string | null  | The URL after redirects, or `null` if the request failed.                  |
| `status`       | integer | null | The HTTP status code, or `null` if the request failed.                     |
| `technologies` | array           | Every detected technology: name, category, version (if found), confidence. |
| `summary`      | object | null  | The shortcuts below, or `null` if the request failed.                      |
| `detectedAt`   | string          | ISO timestamp of when this URL was analyzed.                               |
| `error`        | object | null  | `{ category, message }` if the URL failed, otherwise `null`.               |

**Every requested URL gets exactly one row — never silently dropped.** Even a URL skipped
entirely, because your spending cap was already reached or because this Actor's own run-level
timeout budget (see Limitations) had already tripped before it was ever attempted, still gets a
row: `status: null`, `technologies: []`, and a clear `error` message. Neither case is ever
charged.

`summary` is a set of shortcuts into the most commonly filtered categories, so you don't have to
search the `technologies` array yourself:

| Field                 | Type           | Example                   |
| --------------------- | -------------- | ------------------------- |
| `cms`                 | string | null | `"WordPress"`             |
| `ecommerce`           | string | null | `"Shopify"`               |
| `analytics`           | string\[]       | `["Google Analytics"]`    |
| `tagManagers`         | string\[]       | `["Google Tag Manager"]`  |
| `payments`            | string\[]       | `["PayPal", "Apple Pay"]` |
| `cdn`                 | string\[]       | `["Cloudflare"]`          |
| `hosting`             | string\[]       | `["AWS"]`                 |
| `frameworks`          | string\[]       | `["React"]`               |
| `liveChat`            | string\[]       | `["Intercom"]`            |
| `marketingAutomation` | string\[]       | `["HubSpot"]`             |

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. Here's real
output from a run against `wordpress.org` and `www.allbirds.com`. Each `technologies` array below is
trimmed for readability — showing 3 of the 15 technologies actually detected for `wordpress.org`, and
2 of the 12 for `www.allbirds.com`. `summary` is always computed from the full, untrimmed list, which
is why entries such as Google Tag Manager and React appear there even though they were trimmed out of
`technologies` above:

```json
[
  {
    "url": "https://wordpress.org/",
    "finalUrl": "https://wordpress.org/",
    "status": 200,
    "technologies": [
      {
        "name": "WordPress",
        "slug": "wordpress",
        "version": "7.2",
        "confidence": 100,
        "website": "https://wordpress.org",
        "categories": [
          { "id": 1, "name": "CMS" },
          { "id": 11, "name": "Blogs" }
        ]
      },
      {
        "name": "Nginx",
        "slug": "nginx",
        "version": null,
        "confidence": 100,
        "website": "https://nginx.org/en",
        "categories": [
          { "id": 22, "name": "Web servers" },
          { "id": 64, "name": "Reverse proxies" }
        ]
      },
      {
        "name": "PHP",
        "slug": "php",
        "version": null,
        "confidence": 100,
        "website": "https://php.net",
        "categories": [{ "id": 27, "name": "Programming languages" }]
      }
    ],
    "summary": {
      "cms": "WordPress",
      "ecommerce": null,
      "analytics": [],
      "tagManagers": ["Google Tag Manager"],
      "payments": [],
      "cdn": [],
      "hosting": [],
      "frameworks": ["React"],
      "liveChat": [],
      "marketingAutomation": []
    },
    "detectedAt": "2026-09-20T06:12:53.052Z",
    "error": null
  },
  {
    "url": "https://www.allbirds.com/",
    "finalUrl": "https://www.allbirds.com/",
    "status": 200,
    "technologies": [
      {
        "name": "Shopify",
        "slug": "shopify",
        "version": null,
        "confidence": 100,
        "website": "https://shopify.com",
        "categories": [{ "id": 6, "name": "Ecommerce" }]
      },
      {
        "name": "Cloudflare",
        "slug": "cloudflare",
        "version": null,
        "confidence": 100,
        "website": "https://www.cloudflare.com",
        "categories": [{ "id": 31, "name": "CDN" }]
      }
    ],
    "summary": {
      "cms": null,
      "ecommerce": "Shopify",
      "analytics": [],
      "tagManagers": ["Google Tag Manager"],
      "payments": ["Apple Pay", "PayPal"],
      "cdn": ["Cloudflare"],
      "hosting": [],
      "frameworks": ["React"],
      "liveChat": [],
      "marketingAutomation": []
    },
    "detectedAt": "2026-09-20T06:12:53.063Z",
    "error": null
  }
]
```

### How much does it cost?

You pay **$0.005 per website where at least one technology is detected** ($5 per 1,000). Failed
URLs and pages where nothing is detected are **free**. No start fee.

| Websites with a detection | Cost  |
| ------------------------- | ----- |
| 100                       | $0.50 |
| 1,000                     | $5    |
| 10,000                    | $50   |

### Input

See the **Input** tab for the full configuration. The minimal input is a list of URLs:

```json
{
  "urls": ["https://www.allbirds.com", "wordpress.org", "https://stripe.com"]
}
```

- **`urls`** (required) — 1 to 5,000 URLs or bare domains. `https://` is added automatically if you
  leave the scheme out. **Duplicate entries are processed once**: the list is reduced to each
  distinct site first, so the same URL listed twice is analyzed and billed once, not twice.
- **`includeCategories`** (optional) — keep only technologies in these categories, e.g. `"Ecommerce"`,
  `"CMS"`, `"Analytics"`, `"Payment processors"`. Leave empty to get everything detected.
- **`maxConcurrency`** (optional, default `10`, 1 to 20) — how many websites to analyze in
  parallel.

### Limitations — read this first

This Actor analyzes the HTML that the server actually sends, plus response headers and cookies. It
**does not execute JavaScript**, so technologies that only reveal themselves through browser global
variables set by client-side code may not be detected. Sites that block automated requests return
`error.category: "blocked"` for that URL — and, per the pricing above, that item is **not charged**.
That includes pages that answer with HTTP 200 but serve a bot challenge instead of their content.
This Actor does not attempt to get around such blocks (no proxy rotation, no browser fingerprint
spoofing, no CAPTCHA solving) — a block is treated as a legitimate answer, not an obstacle to defeat.

**Limits.** Each site gets an 8-second analysis budget; a page whose markup is crafted to be
pathologically slow to analyze is abandoned with `error.category: "parse"` and, like every failure,
is not charged. Only the first 2 MB of a response is read, and responses that are not text (a PDF, an
image, an archive) are reported as an error rather than downloaded. URLs that point at private or
internal addresses are refused. Separately, a **run-level budget** protects the whole run from an
input of many pages all crafted to be slow: 25 consecutive detection timeouts in a row (across any
URLs, not just one) stop the run from scheduling further requests, with a clear message in the log —
every URL already analyzed stays in the dataset, and a URL that never got to start this way, or
because your own spending cap was already reached, still leaves its own uncharged row instead of
vanishing.

### FAQ

**Is this legal?** We can't give you legal advice, but here is exactly what the Actor does: it makes
one ordinary HTTP request to each public page you list — the same request a browser makes — and reads
what the server sends back. It does not log in, does not get around blocks, and does not collect
personal data. You are responsible for the URLs you submit and for how you use the results.

**Where do the technology fingerprints come from?** From
[enthec/webappanalyzer](https://github.com/enthec/webappanalyzer), an open-source database of
7,000+ technology signatures, licensed under GPL-3.0. Attribution (`LICENSE-FINGERPRINTS.md`) and the
full GPL-3.0 text (`COPYING`) ship in the Actor's `data/` directory.

**How do I use this from the API or an AI agent (MCP)?** Call it like any other Apify Actor: via the
[Apify API](https://docs.apify.com/api/v2), the JavaScript or Python client, an integration, or an
MCP-connected AI agent that has access to your Apify Actors. Pass the same `urls` input and read the
results back from the default dataset.

**What happens if I hit my spending cap?** The run stops cleanly: it finishes the URLs already in
flight, writes everything it has, and reports `chargeLimitReached: true` in the run summary. Nothing
already produced is lost, and nothing beyond the cap is charged.

### Changelog

**0.1** — Initial release.

# Actor input Schema

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

URLs or bare domains to analyze. "example.com" works — https:// is added for you. Up to 5,000 per run.

## `includeCategories` (type: `array`):

Keep only technologies in these categories, e.g. "Ecommerce", "CMS", "Analytics", "Payment processors". Leave empty for everything.

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

How many websites to analyze in parallel.

## Actor input object example

```json
{
  "urls": [
    "https://www.allbirds.com",
    "https://wordpress.org",
    "https://stripe.com"
  ],
  "maxConcurrency": 10
}
```

# Actor output Schema

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

No description

## `runSummary` (type: `string`):

No description

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plainfetch/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/KTwENaxaecUV768e1/builds/X2FMurVX0jQJd5P7a/openapi.json
