# Tech Stack & Ad Pixel Detector: CMS, GTM Tags, DNS (`lasting_bonnethead/tech-stack-detector`) Actor

Detect any website's tech stack: CMS, ecommerce, analytics, ad pixels (Meta, Google Ads, TikTok, LinkedIn), tag managers, CDN, hosting, payments, chat, email & SaaS tools via DNS. Reads Google Tag Manager tags, extracts tracking IDs, flags changes since last run.

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

## Pricing

from $1.20 / 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.
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 & Ad Pixel Detector

Give it a list of websites and get each one's **live** technology stack:
CMS, ecommerce platform, analytics, **ad pixels** (Meta, Google Ads, TikTok, LinkedIn, X, Pinterest, Reddit, Microsoft/Bing, Criteo…),
tag managers, CDN, hosting, frameworks, payments, live chat, email & marketing tools, A/B testing and consent tools.
Each technology comes with its category, confidence, version (when visible) and **evidence**, which is the exact script, header, cookie, DNS record or GTM tag that matched.

### Why this one

- **It sees inside Google Tag Manager.** Most pixels today are loaded through GTM, so plain-HTML detectors miss them. This Actor downloads the site's public GTM container (the same file every visitor's browser loads) and lists the tags that are actually active, skipping paused ones. On our 30-site test that's how most ad pixels were found without a browser.
- **It extracts tracking IDs**: GTM containers, GA4 measurement IDs, Google Ads conversion IDs, Meta Pixel IDs, TikTok, LinkedIn partner IDs, Pinterest, Snap, X, Microsoft UET, Reddit, Hotjar, Clarity, HubSpot portal, Klaviyo, Segment, Intercom, Shopify store handle, AdSense publisher.
- **DNS intelligence**: email provider (MX), sending tools in SPF (SendGrid, Mailchimp, HubSpot, Salesforce…), DNS host, and SaaS tools that verified the domain in TXT records (Atlassian, DocuSign, Zoom, Slack, OpenAI, Anthropic and dozens more). These are strong B2B buying signals that don't appear in the HTML.
- **Change tracking**: schedule it and get only what changed since the last run, for example "added TikTok Pixel" or "Klaviyo → removed".
- **Live checks.** Every run fetches the site as it is today and shows the evidence for each detection.
- **Polite by design.** It respects robots.txt (RFC 9309, user-agent token `TechStackDetector`), makes at most one request per second per site, and honors Crawl-delay.
- **Pay only for websites that were analyzed successfully.** Blocked, unreachable and robots-disallowed sites are not billed.

### Modes

| Mode | What it does | Speed | Best for |
|---|---|---|---|
| `http` (default) | HTML, headers, cookies, script URLs, meta tags, DNS, GTM container and Shopify pixel config | typically 1–5 s per site | Large lists, lead enrichment |
| `auto` | HTTP first; opens a headless Chrome only when the site is JS-rendered, blocked, or shows no tracking in HTML | mixed | Best balance |
| `browser` | Always renders in headless Chrome, records every network request, reads JS globals and cookies | typically 15–50 s per site | Full ad/analytics audits |

The browser finds **more ad and analytics tags** because it sees everything that fires, including tags injected by other tags. HTTP mode already catches CMS, ecommerce, payments, CDN, hosting and DNS-based tools almost as well as the browser does. See *Accuracy* below.

### Input

```json
{
  "websites": ["allbirds.com", "https://www.hubspot.com", "techcrunch.com"],
  "mode": "http",
  "outputFormat": "domain",
  "monitorChanges": false,
  "includeDns": true,
  "inspectGtm": true,
  "minConfidence": 50
}
```

| Field | Default | Notes |
|---|---|---|
| `websites` | required | Domains or URLs. The given page is analyzed (homepage for a bare domain). Duplicates are removed. |
| `mode` | `http` | `http`, `auto` or `browser` (see above) |
| `outputFormat` | `domain` | `domain` = one rich item per site; `flat` = one CSV-friendly row per site (comma-joined columns per category + `id_*` columns); `rows` = one row per site × technology |
| `monitorChanges` / `onlyChanges` | off | Remember each site's stack in a named key-value store and report `added`, `removed` and `versionChanges` |
| `stateStoreName` | `tech-stack-detector-state` | Use one name per watchlist |
| `inspectGtm` | on | Read tags configured in Google Tag Manager |
| `includeDns` | on | MX / SPF / TXT / NS / reverse DNS |
| `fingerprintSets` | `core`, `community` | See *Fingerprint data* |
| `minConfidence` | 50 | Hide weaker detections |
| `includeEvidence` | on | What matched, per technology |
| `maxConcurrency` | 10 | Also capped by memory (CPU), and by browser slots in browser mode |
| `perSiteTimeoutSecs` | 30 (HTTP) / 60 (Auto, Browser) | Per-step timeout. Timed-out sites are not billed |
| `proxyConfiguration` | none | Optional. Apify Proxy (residential) helps with sites behind bot protection. |

### Output (`domain` format, abridged)

```json
{
  "domain": "allbirds.com",
  "status": "ok",
  "modeUsed": "http",
  "technologyCount": 31,
  "ecommerce": ["Shopify"],
  "analytics": ["Microsoft Clarity", "Meta Pixel", "TikTok Pixel", "Google Ads Conversion Tracking"],
  "advertising": ["Meta Pixel", "Google Ads", "Microsoft Advertising", "Criteo", "Outbrain", "TikTok Pixel"],
  "tagManagers": ["Google Tag Manager"],
  "cdn": ["Cloudflare"],
  "dnsProviders": ["MarkMonitor"],
  "payments": ["Shop Pay", "Apple Pay", "PayPal"],
  "email": ["Microsoft 365"],
  "trackingIds": {
    "gtm": ["GTM-TH8KRSBJ"], "googleAds": ["AW-786649265", "AW-736740096"],
    "microsoftUet": ["5489498", "134398351"], "clarity": ["yrlg0i3snv"], "shopifyStore": ["weareallbirds.myshopify.com"]
  },
  "gtmContainers": [{ "id": "GTM-TH8KRSBJ", "inspected": true, "activeTags": 198, "pausedTags": 30 }],
  "technologies": [
    { "name": "Microsoft Advertising", "categories": ["Advertising"], "confidence": 100, "version": null,
      "evidence": ["GTM GTM-TH8KRSBJ custom HTML tag: …//bat.bing.com/bat.js…"] }
  ],
  "dns": { "mx": ["allbirds-com.mail.protection.outlook.com"], "ns": ["ns2.markmonitor.com", "…"] },
  "checkedAt": "2026-10-08T13:56:00.000Z"
}
```

`status` is one of `ok`, `blocked` (bot protection or 401/403/429), `robots_disallowed`, `dns_not_found`, `timeout`, `fetch_failed`, `http_error`, `invalid_input`. Only `ok` is billed.
DNS-based results are still returned for blocked sites, at no charge.

With `monitorChanges`, items also carry `changeStatus` (`first_check` / `changed` / `unchanged`), `added`, `removed` and `versionChanges`.

A run summary (status counts, top technologies across the list) is saved as `SUMMARY` in the run's key-value store.

### Pricing

Pay per event, no subscription. You are charged **only for websites analyzed successfully** (`status: ok`):

| Event | Price per website |
|---|---|
| Website analyzed (HTTP) | **$0.002** ($2 per 1,000 sites). Lower on paid Apify plans: $0.0018 (Starter), $0.0015 (Scale), $0.0012 (Business) |
| Website analyzed with headless browser (`browser` mode, or `auto` when it opened the browser) | **$0.008**. Lower on paid Apify plans: $0.007 (Starter), $0.006 (Scale), $0.005 (Business) |

Blocked, unreachable, timed-out, robots-disallowed and invalid inputs are free. Change tracking is billed like a normal check, including websites hidden by *Output only websites that changed*. Set **Max total charge** on the run to cap spend; the Actor stops cleanly at the limit.

### Accuracy and limits (please read)

- **Bot protection.** About 15–20% of large consumer brands (e.g. sites behind DataDome, PerimeterX or Akamai Bot Manager) block automated requests from datacenter IPs. Those come back as `blocked` with DNS-only results, and you are not charged. A residential proxy helps in some cases.
- **HTTP vs browser.** In our 30-site test, HTTP mode found 80–95% of what the browser found for CMS, ecommerce, payments, CDN and hosting, but only about 60% of the ad pixels and analytics tags, because many tags are injected at runtime. Use `auto` or `browser` for ad audits.
- **Precision.** In our tests every platform identification we checked was correct (18/18), and a manual review of the evidence put overall precision at an estimated 97% or better. Most remaining errors come from long-tail community fingerprints.
- **DNS TXT verification records can be historical.** A verification record shows the company used a tool at some point, not necessarily today. These have their own `saasVerifiedInDns` field.
- **Confidence** below 100 means a weaker signal (e.g. a CSP header mention is capped at 25 and hidden by default).

### Fingerprint data and license

- The detection engine and the `core` fingerprint set (marketing tags, GTM tag types, DNS/SaaS signals, hosting, major platforms) are this Actor's own work.
- The optional `community` set is the open-source **[webappanalyzer](https://github.com/enthec/webappanalyzer)** fingerprint database (a community continuation of Wappalyzer's open data). It is © its contributors and licensed under **GPL-3.0**. It is downloaded unmodified from its repository at build time and read as a separate data file. Our corrections are kept in our own override file. Thanks to everyone who maintains it.
- This Actor is not affiliated with Wappalyzer, BuiltWith, Google or Meta. Product names are trademarks of their owners.

### Use responsibly

The Actor only reads public pages and public DNS records, the same data any browser sees. It does not log in, bypass paywalls or collect personal data. You are responsible for how you use the results, including complying with applicable laws and the target sites' terms.

# Actor input Schema

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

Domains or URLs to analyze, one per line (e.g. example.com, https://shop.example.com/pricing). The given page is analyzed (homepage if you pass a bare domain).

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

HTTP (fast, cheapest): reads HTML, headers, cookies, scripts, DNS and the tags configured in Google Tag Manager. Auto: HTTP first, then a headless browser only for pages that are JavaScript-rendered or blocked. Browser: always render with headless Chrome to catch every tag that fires on page load (slower, priced higher).

## `outputFormat` (type: `string`):

Domain: one rich item per website (technologies with category, version, confidence and evidence + summary fields). Flat: one row per website with only text columns (best for CSV / Google Sheets / Clay). Rows: one row per website × technology.

## `monitorChanges` (type: `boolean`):

Remembers each website's stack (in a named key-value store) and reports technologies added, removed or upgraded since the previous run. Schedule the Actor to get a tech-change feed.

## `onlyChanges` (type: `boolean`):

With change tracking on, skip websites whose stack did not change (first-time checks are also skipped). Every analyzed website is still billed.

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

Named key-value store that keeps the last known stack per website. Use different names for different watchlists.

## `inspectGtm` (type: `boolean`):

Download the site's public GTM container (the same file every visitor's browser loads) and list the pixels and tags configured in it, without a browser.

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

Look up MX, SPF/TXT, NS and reverse DNS to detect email providers, sending tools (SendGrid, Mailchimp...), SaaS tools that verified the domain (Atlassian, Slack, Zoom, OpenAI...), DNS and hosting providers.

## `fingerprintSets` (type: `array`):

core = this Actor's own fingerprints (marketing tags, DNS, hosting). community = the open-source webappanalyzer fingerprint data (~7,600 technologies, GPL-3.0).

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

Hide detections below this confidence.

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

Show what matched (script URL, header, cookie, DNS record, GTM tag...) for every technology.

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

Websites processed in parallel. Each website is fetched politely (robots.txt respected, at most one request per second per site). In browser mode the concurrency is also capped by memory.

## `perSiteTimeoutSecs` (type: `integer`):

Give up on a website step after this many seconds (the site is then reported with status "timeout" and not billed). Leave empty for the default: 30 s in HTTP mode, 60 s in Auto and Browser mode.

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

Optional. Not needed for most websites.

## Actor input object example

```json
{
  "websites": [
    "allbirds.com",
    "wordpress.org",
    "https://www.zillow.com"
  ],
  "mode": "http",
  "outputFormat": "domain",
  "monitorChanges": false,
  "onlyChanges": false,
  "stateStoreName": "tech-stack-detector-state",
  "inspectGtm": true,
  "includeDns": true,
  "fingerprintSets": [
    "core",
    "community"
  ],
  "minConfidence": 50,
  "includeEvidence": true,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `overview` (type: `string`):

No description

## `summary` (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 = {
    "websites": [
        "allbirds.com",
        "wordpress.org",
        "https://www.zillow.com"
    ]
};

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

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

```

## MCP server setup

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