# Tech Stack Detector API: Wappalyzer & BuiltWith Alternative (`kittiwake/tech-stack-detector`) Actor

Detect the tech stack of any list of domains — CMS, e-commerce platform, analytics, CDN, frameworks, hosting — with versions and confidence. Watch mode reports technologies added or removed since the last run.

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

## Pricing

from $2.80 / 1,000 domain analyzeds

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 Detector API: Wappalyzer & BuiltWith Alternative

**See what any website is built with.** A **tech stack detector** for a whole list of domains: CMS,
e-commerce platform, analytics, tag managers, CDN, web server, frameworks, hosting and email
provider, with versions where the site exposes them and a confidence score for each. A
pay-per-domain **Wappalyzer alternative** and **BuiltWith alternative** with no subscription, and a
**watch mode** that tells you when a site adds or drops a technology — "competitor moved from
Shopify to BigCommerce".

Paste the domains, run once for a snapshot, or schedule it weekly in watch mode and receive only
the changes.

### What it is for

- **Sales and lead qualification** — filter a prospect list by stack: every Shopify store, every
  site still on an old WordPress, every company using HubSpot or Salesforce.
- **Agencies** — audit a client's or a prospect's stack before the first call.
- **Competitive intelligence** — watch competitors and get a row when they switch platform, add a
  new analytics tool or change CDN.
- **Market research** — count technology share across a list of domains.

### Quick start

**One-off scan of a list:**

```json
{
  "domains": ["shopify.com", "wordpress.org", "stripe.com"]
}
```

**Weekly watch — only baselines and changes:**

```json
{
  "domains": ["competitor-one.com", "competitor-two.com"],
  "mode": "watch",
  "stateStoreName": "competitor-watch"
}
```

The first run of a domain records a **baseline**. From then on, watch mode writes a row only when a
technology is added, removed or changes version, or when the domain could not be read.

### Input

| field | type | default | what it does |
|---|---|---|---|
| `domains` | string\[] | **required** | Domains or URLs. Only the host is used. Duplicates are read once; IP addresses and single-label names are skipped and listed in `RUN_SUMMARY` |
| `mode` | `scan` | `watch` | `scan` | `scan`: a row for every domain. `watch`: rows only for baselines, changes and failures |
| `includeDns` | boolean | `true` | Also read MX, NS, TXT and SOA records to detect email, DNS and verification services. Matched only, never output |
| `stateStoreName` | string | `tech-stack-detector-state` | Named key-value store that keeps each domain's last stack. Use one name per list |
| `requestDelayMs` | integer | `500` | Pause before each domain after the first |
| `maxDomains` | integer | `1000` | Domains past this are not read and not charged |

### Output

One row per domain:

```json
{
  "domain": "wordpress.org",
  "finalUrl": "https://wordpress.org/",
  "httpStatus": 200,
  "status": "ok",
  "technologies": [
    { "name": "Nginx", "categories": ["Web servers", "Reverse proxies"], "version": null, "confidence": 100 },
    { "name": "PHP", "categories": ["Programming languages"], "version": null, "confidence": 100 },
    { "name": "WordPress", "categories": ["CMS", "Blogs"], "version": "7.2", "confidence": 100 }
  ],
  "technologyNames": ["Nginx", "PHP", "WordPress"],
  "categories": { "Blogs": ["WordPress"], "CMS": ["WordPress"], "Programming languages": ["PHP"], "Reverse proxies": ["Nginx"], "Web servers": ["Nginx"] },
  "changeType": ["added", "removed"],
  "added": ["WordPress"],
  "removed": ["Drupal"],
  "versionChanges": [],
  "checkedAt": "2026-09-26T07:20:41.134Z",
  "source": "https://wordpress.org/"
}
```

- `status` is `ok`, `fetch-failed` (no answer, a timeout, or HTTP 400 and above) or
  `robots-disallowed` (the site's robots.txt closes its homepage to this Actor). Failed rows carry no
  technologies and are not charged.
- `changeType` is `baseline` on a domain's first run, then any of `added`, `removed`,
  `version-changed`, or empty when nothing changed.
- `confidence` is 0–100. Some fingerprints are hints rather than proof, and say so with a lower
  confidence; filter on it if you only want certain matches.
- A run summary — domains analysed, failures, robots refusals, changes, the fingerprint snapshot
  used — is saved as `RUN_SUMMARY` in the run's key-value store.

### What you pay for

| event | price | when |
|---|---|---|
| **Domain analyzed** | $0.004 | a domain's homepage read, matched and its state saved — only when all of it succeeds |
| Tech change detected | $0.02 | a technology was added or removed since the domain's last successful run. Once per domain per run, however many changed. Never on the first run, not for a version change alone |

1,000 domains cost about **$4**. Apify Store discounts apply by subscription plan: the prices above
are Free-plan prices, and Bronze, Silver and Gold plans pay less on every event.

Nothing is charged for a domain whose robots.txt closes it, a failed request, an error page or an
invalid domain. The run stops at the spending limit you set; rows already saved are yours.

### How it works, and what it does not do

- **One request per domain, plus robots.txt.** It reads `robots.txt` first and stops if `/` is
  disallowed for it (or for `*`), then makes ONE GET of the homepage, following redirects, with a
  15-second timeout and an identifying User-Agent:
  `kittiwake-tech-stack-detector/0.1 (+https://apify.com/kittiwake/tech-stack-detector)`.
  No crawling past the homepage, no proxies, no header rotation, no retries.
- **It matches what the server sends**: response headers, cookie *names*, meta tags, script URLs,
  the page's HTML, the final URL and, optionally, DNS records.
- **It does not run JavaScript.** Technologies that only show up after scripts run in a browser
  (and are not referenced in the HTML) are not detected. That is the trade for speed and a low
  price per domain.
- **No page text, no emails, no personal data** in the output: technology names, categories,
  versions and confidence only. Cookie values are never read.
- A redirect to another host is followed, but that host's robots.txt is not fetched separately.

### Fingerprints and licence

Detection uses the open fingerprint set **[enthec/webappanalyzer](https://github.com/enthec/webappanalyzer)**
— a community-maintained continuation of the Wappalyzer fingerprints — vendored as a pinned,
unmodified snapshot and refreshed from upstream. That data is licensed **GPL-3.0**; credit for it
belongs to its contributors. This Actor is not affiliated with Wappalyzer, BuiltWith or enthec.

### FAQ

**Is this a Wappalyzer alternative?** It uses the same open fingerprint format, maintained as
enthec/webappanalyzer, without a browser extension or a subscription: you pay per domain.

**How is it different from BuiltWith?** BuiltWith sells subscriptions and historical lookups from
its own crawl. This Actor reads the site live when you run it, and watch mode builds your own
change history from then on.

**Why is a technology I know the site uses missing?** It is probably loaded by JavaScript after the
page renders, or only on pages other than the homepage. This Actor reads the homepage HTML and
headers only.

**Why did a domain come back `robots-disallowed`?** Its robots.txt disallows `/` for all robots or
for this one. The Actor respects that, writes the row so you know, and charges nothing.

**Can I export to CSV or Excel?** Yes. The dataset downloads as JSON, CSV, Excel or XML, or you can
read it over the Apify API. The *Technologies with versions* view gives one line per technology.

### Support

Use the **Issues** tab on this Actor. Include the run id and the input you used.

# Actor input Schema

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

Domains or URLs to analyse, e.g. example.com or https://www.example.com/pricing. Only the host is used: the Actor reads that host's homepage once. Duplicates are read once; IP addresses and single-label names are skipped and listed in the run summary.

## `mode` (type: `string`):

scan: one row per domain, every run. watch: a row only on a domain's first run (baseline), when a technology is added, removed or changes version, or when the domain cannot be read — schedule it and get only the news.

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

Also read the domain's MX, NS, TXT and SOA records to detect email, DNS and verification services. The records are matched only, never written to the dataset.

## `stateStoreName` (type: `string`):

Named key-value store that keeps each domain's last detected stack between runs, so changes can be reported. Use a different name per list if you run several.

## `requestDelayMs` (type: `integer`):

Pause before each domain after the first.

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

Domains past this number are not read and not charged; the run summary counts them.

## Actor input object example

```json
{
  "domains": [
    "shopify.com",
    "wordpress.org",
    "stripe.com"
  ],
  "mode": "scan",
  "includeDns": true,
  "stateStoreName": "tech-stack-detector-state",
  "requestDelayMs": 500,
  "maxDomains": 1000
}
```

# Actor output Schema

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

No description

## `changes` (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 = {
    "domains": [
        "shopify.com",
        "wordpress.org",
        "stripe.com"
    ]
};

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

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

```

## MCP server setup

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