# Tech Stack Detector - Website Technology Lookup (`fanndev/tech-stack-detector`) Actor

Detect what a website runs - store platform, CMS, framework, analytics, marketing, chat, payments, CDN - across 87 fingerprints, with the exact header, cookie or script that proved each one. Filter a domain list to a stack, or find who is missing the tool you sell. One request per site.

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

## Pricing

from $2.00 / 1,000 results

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 — Website Technology Lookup

Give it a list of domains. Get back what each one runs — store platform, CMS, framework, analytics, marketing, chat, payments, CDN — **and the exact header, cookie or script tag that proved it**.

87 technologies across 13 categories. One request per site. No login, no API key, no proxy.

### Why the evidence matters

Most stack lookups hand you a label and ask you to trust it. This one shows its working:

```json
{
  "technology": "Shopify",
  "confidence": "confirmed",
  "evidence": ["cookie _shopify_y", "asset host cdn.shopify.com", "html contains \"Shopify.theme\""]
}
```

Every detection carries `confidence`:

- **`confirmed`** — proven by something that cannot match by coincidence: a vendor cookie, a vendor CDN hostname, a vendor-specific header.
- **`likely`** — consistent with the technology but shared with others, or self-declared.

So when a result surprises you, you can check it in ten seconds instead of re-running a second tool to see whether you believe the first one.

### The two jobs it does

**Prospecting — find the companies on a stack you sell into.**

```json
{
  "domains": ["allbirds.com", "gymshark.com", "notion.com"],
  "requireTechnology": ["Shopify"]
}
```

**Gap-finding — find the companies missing the tool you sell.** This is the higher-value one, and it is two fields:

```json
{
  "domains": ["...500 store domains..."],
  "requireTechnology": ["Shopify"],
  "excludeTechnology": ["Klaviyo", "Omnisend", "Mailchimp", "Attentive"]
}
```

That returns Shopify stores with no email platform detected — a qualified list, not a scraped one.

### What you get per site

| Field | What it tells you |
|---|---|
| `technologies` / `technologiesConfirmed` | Everything detected, and the subset that is proven |
| `stackEcommerce`, `stackCMS`, `stackFramework`, … | One column per category, so the CSV filters cleanly |
| `detections` | Per technology: confidence, signal types, exact evidence strings |
| `reachable` / `_error` | Whether the domain answered, and why not if it did not |
| `redirected` / `finalDomain` | Whether you are looking at the stack of a *different* host |
| `technologyCount` / `confirmedCount` | Quick quality score for the row |

The `RUN_SUMMARY` record ranks technologies across the whole list with a site count and share — useful on its own for a market snapshot ("what share of these 400 DTC brands run Klaviyo").

### Categories covered

Ecommerce · CMS · Framework · Hosting/CDN · Analytics · Tag Manager · Marketing · Support · Payments · Reviews · Search · Monitoring · Experimentation

Coverage is deliberately narrow. Wappalyzer tracks about 2,000 technologies and most of them never appear in a lead filter; this table covers the ones buyers actually sort on, which keeps every fingerprint maintained rather than inherited.

### Limits worth knowing

- **Homepage only.** Fingerprinting is done on the homepage plus, optionally, `/robots.txt`. A tool that only loads on a checkout or pricing page will not be seen. This is what keeps it one request per domain.
- **No JavaScript execution.** Technologies injected by a tag manager at runtime rather than referenced in the HTML are invisible. `Google Tag Manager` will be detected; what it loads afterwards may not be.
- **Absence is not proof of absence.** A site that does not show Klaviyo may still use it. Read `excludeTechnology` results as "no evidence found", which for prospecting is usually the right signal anyway.
- **Redirects change the subject.** If `example.com` redirects to `shop.example.net`, the stack reported is the destination's. `redirected` and `finalDomain` tell you when this happened.
- **`likely` is not `confirmed`.** Filters match on technology name regardless of confidence, so check `confidence` before acting on a thin result.

### Failures are data

A domain that does not resolve is a finding about that prospect, not a broken run. Errors are classified rather than retried into the ground:

`dns_failure` · `tls_failure` · `timeout` · `connection_failed` · `http_error` · `unreachable`

DNS and TLS failures return immediately — they will not fix themselves on retry. Sites that answer only over plain HTTP are retried there automatically, so older business sites stay in the dataset instead of being written off.

### Cost and proxy

One request per domain, two with `checkRobotsTxt`. **Leave the proxy off by default** — there is no single target site here, so there is nothing to be blocked by. Turn it on only when scanning thousands of domains and you want the requests spread across IPs.

`concurrency` can go high (up to 30) because every request goes to a different server.

### Output shape

Every record carries `_input`, `_source`, `_scrapedAt` and `recordType`. `recordType` is `SITE` per domain, `RUN_SUMMARY` once per run, `ERROR` for a domain that could not be fetched. Three dataset views: **Overview**, **Marketing stack** and **Infrastructure**. Optional `exportFormats` writes JSON, NDJSON, CSV or XLSX to the key-value store.

Pairs with `shopify-store-analyzer`: use this to find the Shopify stores in a list, then run that one for the catalogue.

### Development

```bash
pip install -r requirements.txt
python -m src            # needs an Apify runtime
python test_local.py     # table integrity, matching edge cases, live fixtures
```

`CRAWLING_METHOD.md` documents the signal types and why each one is weighted the way it is.

# Actor input Schema

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

One domain or URL per line. 'allbirds.com', 'www.allbirds.com' and 'https://allbirds.com/collections/mens' are all accepted; paths are dropped because the homepage is what gets fingerprinted.

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

The same targets as a URL list, for pasting straight out of another actor's dataset.

## `requireTechnology` (type: `array`):

Keep only sites running at least one of these technologies, matched by name (e.g. 'Shopify', 'Klaviyo'). This is the prospecting filter.

## `excludeTechnology` (type: `array`):

Drop sites running any of these. Pair with the filter above to find gaps - for example require 'Shopify' and exclude 'Klaviyo' to list stores with no email platform.

## `minTechnologies` (type: `integer`):

Drop sites with fewer detections than this. Useful for skipping parked domains and holding pages.

## `onlyReachable` (type: `boolean`):

Drop domains that could not be fetched. Leave this off when cleaning a list - an unreachable domain is a useful finding.

## `checkRobotsTxt` (type: `boolean`):

One extra request per site. Catches platform-specific disallow paths on sites that render everything client-side and give the homepage away nothing.

## `includeEvidence` (type: `boolean`):

Keep the 'detections' array showing exactly which header, cookie, host or markup string proved each technology. Turn off for a slimmer dataset.

## `concurrency` (type: `integer`):

How many sites to fetch at once. Each target is a different server, so this can go high without pressuring any one of them.

## `emitSummary` (type: `boolean`):

Append one RUN\_SUMMARY record ranking which technologies appear across the whole list and on what share of sites.

## `exportFormats` (type: `array`):

Also write the results to the key-value store in these formats. The dataset is always produced regardless.

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

Optional. There is no single target site here, so there is nothing to be blocked by and the direct connection is normally fine. Enable it if you are scanning thousands of domains and want the requests spread across IPs.

## Actor input object example

```json
{
  "domains": [
    "allbirds.com",
    "notion.com",
    "wordpress.org"
  ],
  "onlyReachable": false,
  "checkRobotsTxt": true,
  "includeEvidence": true,
  "concurrency": 8,
  "emitSummary": true,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Every site analysed, the technology ranking and any error records from this run.

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

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

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

```

## MCP server setup

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