# Website Tech Stack Detector for Lead Enrichment (`isnard/website-tech-detector`) Actor

Detect the CMS, analytics, ad pixels, JavaScript frameworks, CDN, payments, live chat and email provider of a list of websites. One passive request per site, with the evidence for every match.

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

## Pricing

from $2.00 / 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.

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

## Website Technology Detector

Give it a list of domains. For each one you get the technologies the homepage uses (CMS, analytics, ad pixels, JavaScript frameworks, CDN and hosting, payments, live chat), the email provider from the MX records, and a short security summary (HTTPS yes or no, which common security headers are missing). Every detected technology comes with the evidence that matched, so you can check it yourself.

Detection is passive: one GET request to the homepage (redirects followed) and one DNS MX query per domain. No crawling, no login, no form submission. The Actor does not extract emails, phone numbers or any other personal data.

### Who it is for

- **B2B sales teams**: enrich a lead list with the stack each company runs, then filter (for example "Shopify stores without Meta Pixel" or "WordPress sites using Microsoft 365").
- **Agencies and freelancers**: find sites on a platform you specialize in, or on an old stack you can migrate.
- **SaaS and integration vendors**: check which prospects already use a tool you integrate with.
- **Market research**: count platform share across a list of competitors or a whole niche.

### What it detects

| Category | Technologies |
| --- | --- |
| CMS and ecommerce | WordPress, WooCommerce, Shopify, Wix, Squarespace, Webflow, Drupal, Joomla, Magento, PrestaShop, Tiendanube, VTEX |
| Analytics and ads | Google Analytics (Universal), Google Analytics 4, Google Tag Manager, Meta Pixel, Hotjar, Microsoft Clarity, HubSpot, LinkedIn Insight Tag |
| Frameworks and libraries | React, Next.js, Vue.js, Nuxt, Angular, jQuery, Bootstrap, Tailwind CSS |
| CDN and hosting | Cloudflare, Akamai, Fastly, Vercel, Netlify, Amazon CloudFront |
| Payments | Stripe, PayPal, Mercado Pago |
| Live chat | Intercom, Zendesk, Tawk.to, WhatsApp widget |
| Email provider (MX) | Google Workspace, Microsoft 365, Zoho Mail, or "Other email provider" |

Signals used: page HTML, `<script src>` URLs, `<meta>` tags (for example `generator`), response headers (for example `server`, `x-powered-by`, `cf-ray`), cookie names from `Set-Cookie` (values are never stored) and MX hostnames.

The security summary checks the final response for `Strict-Transport-Security`, `Content-Security-Policy`, `X-Frame-Options`, `X-Content-Type-Options`, `Referrer-Policy` and `Permissions-Policy`.

### Input

```json
{
    "domains": ["wordpress.org", "https://www.shopify.com/", "mercadolibre.com.ar"],
    "timeoutSecs": 15
}
```

- `domains` (required): domains or URLs. URLs are reduced to the hostname and a leading `www.` is removed, then `https://<domain>/` is fetched. Duplicates are checked once. Invalid entries are skipped with a warning in the log and are not charged.
- `timeoutSecs` (default 15): time limit for the homepage request and for the MX query.

If HTTPS cannot connect at all (or the certificate is invalid), the Actor tries `http://<domain>/` once and reports the HTTPS problem in `errors`.

### Output

One dataset item per domain. Shortened example (real output for 5 domains is in `ejemplos/salida.json` in the source):

```json
{
    "input": "vercel.com",
    "domain": "vercel.com",
    "checked_at": "2026-10-29T14:02:11+00:00",
    "final_url": "https://vercel.com/",
    "status_code": 200,
    "title": "Agentic Infrastructure - Vercel",
    "technologies": [
        { "name": "Google Workspace", "category": "Email provider", "evidence": ["mx: aspmx.l.google.com"] },
        { "name": "Vercel", "category": "Hosting", "evidence": ["header server: Vercel", "header x-vercel-cache: HIT"] },
        { "name": "Next.js", "category": "JavaScript framework", "evidence": ["html: /_next/static/", "header x-powered-by: Next.js, Payload"] },
        { "name": "React", "category": "JavaScript framework", "evidence": ["implied by Next.js"] }
    ],
    "technology_names": ["Google Workspace", "Vercel", "Next.js", "React"],
    "email_provider": "Google Workspace",
    "mx_hosts": ["aspmx.l.google.com", "alt1.aspmx.l.google.com"],
    "security": {
        "https": true,
        "security_headers_present": ["Strict-Transport-Security", "..."],
        "security_headers_missing": ["Permissions-Policy"],
        "security_headers_missing_count": 1
    },
    "errors": []
}
```

`technology_names` and `email_provider` repeat values from `technologies` so CSV and Excel exports are easy to filter.

Problems are reported in `errors` for that domain instead of failing the run. A domain that does not exist or does not answer still gets a result, with empty `technologies`.

### Adding a technology

Rules live in `my_actor/rules.json`. Each entry has a category and one or more case-insensitive regex patterns:

```json
"Tawk.to": {
    "category": "Live chat",
    "html": ["embed\\.tawk\\.to"]
}
```

Available keys: `html`, `scripts`, `meta` (tag name to pattern), `headers` (lowercase header name to pattern, empty string means "header exists"), `cookies` (cookie name pattern), `mx` (MX hostname pattern) and `implies` (other technologies to add). Add a small HTML sample to `tests/fixtures/` and a line to `tests/test_detect.py`; a test fails if a rule has no test.

### Pricing

Pay per event: you pay for each domain analyzed (`domain-analyzed` event), including domains that turn out to be unreachable, because the requests were still made. Invalid input lines are not charged. You can cap the cost of a run with the "Maximum cost per run" setting; the Actor stops cleanly when the cap is reached.

### Limitations

- Only the homepage is read. Tools loaded on other pages (checkout, blog, app) are not seen.
- Scripts that are injected later by JavaScript (for example tags added by Google Tag Manager) are not seen, because the page is not rendered in a browser. A site with GTM may also run GA4, Meta Pixel or Hotjar without them showing up here.
- React and Vue apps built without server rendering often leave no trace in the HTML. Tailwind is detected only when the CDN script or inline `--tw-` variables are present.
- Cookies come from the final response only. Cookies set during redirects or by JavaScript are not seen.
- CDNs are detected from response headers. A site behind a CDN that hides its headers is not reported.
- "Other email provider" means MX records exist but match none of the known providers. Many companies route mail through a filter (Proofpoint, Mimecast) in front of Google or Microsoft, and then show up as "Other".
- Some sites block automated requests or show a challenge page. The result then reflects that page (often only the CDN is detected), and `status_code` is usually 403 or 503.
- Results are a snapshot at the time of the run.

### Run locally

```bash
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt   # on Linux/macOS: .venv/bin/pip
## put input in storage/key_value_stores/default/INPUT.json
.venv/Scripts/python -m my_actor
python -m unittest discover -s tests -t .
```

Author: Matias Isnard.

# Actor input Schema

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

Domains or URLs, one per line. URLs are reduced to their hostname and a leading www. is removed (https://www.example.com/page becomes example.com). The homepage https://<domain>/ is fetched and redirects are followed. Duplicates are checked once.

## `timeoutSecs` (type: `integer`):

Maximum time for the homepage request and for the DNS MX query.

## Actor input object example

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

# Actor output Schema

## `results` (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": [
        "wordpress.org",
        "shopify.com"
    ]
};

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

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

```

## MCP server setup

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