# Website Technology Lookup & Tech Stack Detector (`postatelier/website-tech-stack`) Actor

Find out what any website is built with: CMS, ecommerce platform, frameworks, analytics, CDN, payments, email provider and 7,600+ more technologies, with version and confidence. Bulk URLs, any page (not just the homepage). $2 per 1,000 sites; failed sites are free.

- **URL**: https://apify.com/postatelier/website-tech-stack.md
- **Developed by:** [Post Atelier](https://apify.com/postatelier) (community)
- **Categories:** Developer tools, Lead generation, SEO 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 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

### What does Website Technology Lookup do?

Give it a list of websites and get back what each one is built with: CMS, ecommerce platform, JavaScript frameworks, analytics and ad tags, CDN and hosting, payment providers, email provider, and more. There is one row per technology, with its category, version (when the site reveals it), a confidence score and the evidence it was detected by.

It uses a fingerprint set of **7,647 technologies in 109 categories**, in the Wappalyzer format (the open [webappanalyzer](https://github.com/enthec/webappanalyzer) fork, updated regularly). It checks any page you give it, not just the homepage, so `https://shop.example.com/checkout` is checked as that page.

- **Bulk:** up to 10,000 URLs per run.
- **Fast HTTP mode** by default. **Browser mode** (`renderJs`) also reads JavaScript globals and background requests.
- **Monitoring:** with one row per technology, reports only the technologies a site added or dropped since the last run.
- **Failed sites are free.** You pay only for sites where at least one technology was found on the website itself. DNS-only results are free.

### What you get

| Field | Example | Notes |
|---|---|---|
| `technology` | `WordPress` | Name as in the fingerprint set |
| `version` | `6.9.10` | `null` when the site doesn't reveal it |
| `category`, `categories` | `CMS`, `["CMS", "Blogs"]` | Primary category first |
| `confidence` | `100` | 0–100. Weak, single-hint matches are below 50 and are dropped by default |
| `detectedBy` | `["meta:generator", "scriptSrc"]` | The evidence: headers, cookies, meta tags, script URLs, HTML, DOM selectors, JS globals, DNS records |
| `impliedBy` | `["WordPress"]` | Filled in when the technology is inferred from another one (e.g. WordPress implies PHP and MySQL) |
| `foundIn` | `website` or `dns` | `dns` = seen only in the domain's DNS records (see below) |
| `website`, `cpe`, `saas`, `oss`, `pricing` | `https://wordpress.org`, `cpe:2.3:a:wordpress:wordpress:...` | Vendor metadata from the fingerprint set; `cpe` helps match against vulnerability databases |
| `url`, `finalUrl`, `domain` | `https://techcrunch.com`, `https://techcrunch.com/`, `techcrunch.com` | `finalUrl` is the URL after redirects |
| `status`, `error` | `ok` / `no_technologies_detected` / `error` | Errors say why: DNS failure, timeout, HTTP 403 (bot protection) |

**About `foundIn: dns`:** with `checkDns` on, the Actor also reads the domain's MX, TXT, NS and SOA records. They reveal:

- the email provider (Google Workspace, Microsoft 365);
- the DNS host (Cloudflare, Route 53);
- SaaS tools the company verified its domain with (for example DocuSign, Atlassian, HubSpot, Zendesk or OpenAI).

These are useful sales signals, but they describe the company's accounts, not the website's code, so they are labelled separately and sorted last.

### Run it on a schedule: get an alert when a site changes its stack

A site switching platforms (Magento to Shopify, Universal Analytics to GA4, adding Klaviyo or Intercom) is a common sales and competitor-tracking trigger. Set it up once:

1. **Save a task.** Fill in the input (or paste the JSON below into the JSON editor) and click **Save as a new task**.
   ```json
   { "urls": ["https://www.allbirds.com", "https://www.gymshark.com"], "monitorChanges": true, "monitorName": "prospects" }
   ```
2. **Schedule it weekly.**
   - Open **Schedules** in the left menu of the Apify Console, create a schedule and add your task.
   - The first run outputs a baseline (`change: "baseline"`). Later runs output only rows with `change: "added"` or `change: "removed"`, plus `error` rows for sites that couldn't be checked.
   - If nothing changed and every site was reachable, the dataset is empty.
   - With `outputFormat: "sites"`, every site gets a row, with `added` and `removed` lists.
3. **Send the results somewhere.** In the task's **Integrations** tab:
   - **Slack:** "Send notification via Slack" when a run succeeds.
   - **E-mail or Google Drive:** "Send results email via Gmail" attaches the rows; "Upload results to GDrive" saves them as a file.
   - **Google Sheets, CRM, n8n:** connect the task through Make, Zapier or n8n (listed under Automation platforms) and add a row per result.
   - **Webhook:** add an HTTP webhook for the event **Run succeeded** with your URL. The payload includes `resource.defaultDatasetId`; fetch the rows from `https://api.apify.com/v2/datasets/<defaultDatasetId>/items`.

Monitoring still charges per site checked, because each site is fetched and analyzed on every run. If a page that had website technologies suddenly shows none (usually a block page), the previous result is kept and the site is not charged. Changing `categories`, `minConfidence`, `checkDns` or `renderJs` starts a new baseline.

### Who uses it

- **Sales and lead-gen teams:** filter a prospect list to "uses Shopify" or "runs WordPress with WooCommerce", or enrich CRM accounts with their ecommerce platform and email provider.
- **Agencies and freelancers:** find sites on outdated versions or a platform you specialise in.
- **Product and competitive research:** see which analytics, A/B testing or chat tools competitors run, and get told when that changes.
- **Security and IT teams:** inventory versions across your own sites (`cpe` maps to CVE databases).
- **AI agents:** one call answers "what is this site built with?" with structured, evidence-backed rows.

### How to use it

1. Paste websites into **Websites**, one per line. Domains (`example.com`) and full page URLs both work.
2. Optional: turn on **Render JavaScript** for more complete results on modern JavaScript sites.
3. Optional: limit **Only these categories** (e.g. `Ecommerce`, `CMS`, `Analytics`, `Payment processors`), or switch **Output format** to one row per website.
4. Click **Start** and download the results as JSON, CSV or Excel, or read them over the API.

**HTTP mode or browser mode?** One measured run per mode on allbirds.com, on Apify, 2026-10-20:

| | HTTP mode (default) | Browser mode (`renderJs: true`) |
|---|---|---|
| Technologies found | 15 (10 from the website, 5 from DNS) | 30 (25 from the website, 5 from DNS) |
| Only found in browser mode | | Vue.js, GSAP, core-js, Framer Motion, Microsoft Clarity, Facebook Pixel, Google Analytics, DoubleClick Floodlight, Datadog, OneTrust, Attentive, Shop Pay, Impact and others |
| Time | about 1 s for the site (2.3 s for the whole run) | about 20 s for the first site, including browser start |
| Price per site | $0.002 | $0.005 |

HTTP mode reads the HTML, headers, cookies, meta tags, script URLs and DOM. It also finds globals that the page declares inline (such as Next.js's `__NEXT_DATA__`). Browser mode adds runtime JavaScript globals (`React.version`, `Shopify`, `gtag`...), background requests and late-loading tags.

### Output example

One row per technology (default), from a real run on `https://www.gymshark.com` with `categories: ["Ecommerce", "CMS", "Payment processors", "Analytics"]`. Shortened: real rows also have `website`, `cpe`, `saas`, `oss`, `pricing` and `checkedAt`.

```json
{
  "url": "https://www.gymshark.com",
  "finalUrl": "https://www.gymshark.com/",
  "domain": "gymshark.com",
  "technology": "Contentful",
  "version": null,
  "confidence": 100,
  "categories": ["CMS"],
  "detectedBy": ["html", "scripts"],
  "impliedBy": [],
  "foundIn": "website",
  "category": "CMS",
  "status": "ok",
  "mode": "http"
}
```

The same run also returned Shopify, Afterpay, Cart Functionality and LinkedIn Insight Tag from the website, plus Mixpanel from DNS (`foundIn: "dns"`).

With `outputFormat: "sites"`, you get one row per website. This is from a real run on `https://techcrunch.com`, shortened:

```json
{
  "url": "https://techcrunch.com",
  "domain": "techcrunch.com",
  "status": "ok",
  "technologyCount": 31,
  "byCategory": {
    "CDN": ["Cloudflare"],
    "CMS": ["WordPress 6.9.10"],
    "Marketing automation": ["Sailthru"],
    "PaaS": ["WordPress VIP"],
    "SEO": ["Yoast SEO 25.1", "Yoast SEO Premium 25.1"],
    "Security": ["HSTS", "reCAPTCHA"],
    "Tag managers": ["Google Tag Manager"]
  },
  "dnsServices": ["Atlassian Cloud", "Zendesk", "HubSpot", "DocuSign", "Figma", "Microsoft 365", "Airtable"],
  "technologies": [
    { "technology": "WordPress", "version": "6.9.10", "confidence": 100, "categories": ["CMS", "Blogs"], "foundIn": "website" }
  ]
}
```

### Use it from code, Make, Zapier or an AI agent

```bash
curl -X POST "https://api.apify.com/v2/acts/postatelier~website-tech-stack/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.allbirds.com"], "categories": ["Ecommerce"]}'
```

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("postatelier/website-tech-stack").call(run_input={"urls": ["wordpress.org", "techcrunch.com"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["domain"], row["technology"], row.get("version"))
```

It works with the Apify MCP server, so AI agents can call it as a tool, and with the Apify apps for Make, Zapier and n8n.

### Pricing

Pay per event, no subscription:

| Event | Price | Per 1,000 |
|---|---|---|
| Website analyzed (at least one technology found on the website) | $0.002 | $2.00 |
| Browser rendering (only with `renderJs: true`) | $0.003 extra | $3.00 extra |

Sites that fail (DNS error, timeout, blocked, HTTP error), or where nothing is found on the website itself (DNS-only results included), are **not charged**. A challenge or placeholder page that returns HTTP 200 can still show real technologies, such as its CDN, and is then charged. Examples:

- 1,000 prospect domains in HTTP mode: about **$2**.
- 500 sites in browser mode: 500 × $0.005 = **$2.50**.
- A weekly monitor of 200 competitor sites: 200 × $0.002 × 4 = about **$1.60 a month**.

Apify's free plan includes a monthly usage credit that covers small lists.

### How detection works, and the fingerprint licence

- **Fingerprints.** The technology definitions come from [enthec/webappanalyzer](https://github.com/enthec/webappanalyzer), the community continuation of Wappalyzer's open fingerprints.
  - That repository is licensed **GPL-3.0** (the GNU General Public License v3, not the AGPL). We checked its LICENSE file on 2026-10-20.
  - This Actor uses a pinned snapshot hosted on Apify; we don't distribute the fingerprint files.
  - Each run's `SUMMARY` record shows the snapshot's commit and date. We update the snapshot by hand from the upstream repository. Thanks to the webappanalyzer maintainers and contributors.
- **Matching engine.** It is written for this Actor and follows Wappalyzer's rules:
  - Pattern confidences add up to 100.
  - Versions come from the matched text.
  - `implies`, `requires` and `excludes` are resolved, so WooCommerce is only reported on a WordPress site.
- **Scope.** Each URL is one page, with no crawling. Run several pages of the same site (homepage, `/pricing`, `/checkout`) to see more.

### Good to know

- **Bot protection.** Sites behind strict bot protection may answer 403 or a challenge page. Those come back as `error` (not charged). The Actor doesn't solve CAPTCHAs or bypass protection.
- **Versions.** Versions appear only when the site exposes them (a generator tag, a versioned script URL, a JS global). Many sites hide them.
- **Hint-based matches.** A few fingerprints match on hints such as domains listed in a `Content-Security-Policy` header. Check `detectedBy` and `confidence` when a single result matters.
- **Public sites only.** Private, local and cloud-metadata addresses are refused. You are responsible for checking only sites you're allowed to access.

### Related Actors

- [EU Company Registry Lookup](https://apify.com/postatelier/eu-company-registry): company records and VAT checks from official EU registers.
- [UK Companies House](https://apify.com/postatelier/uk-companies-house): UK company profiles, filings and new-company monitoring.
- [Secretary of State Business Entity Search](https://apify.com/postatelier/us-business-entities): US business entities from 9 states.

### FAQ

**Is this the same as Wappalyzer?** It uses the same fingerprint format and the open community fork of the fingerprints, with our own matching engine. Results are close to Wappalyzer's but not identical.

**Why do I see AI and SaaS companies such as OpenAI or Atlassian for a website?** Those come from DNS TXT records the company added to verify its domain with that service (`foundIn: "dns"`, `detectedBy: ["dns:TXT"]`). Turn `checkDns` off, or filter on `foundIn: "website"`, if you only want the website's own stack.

**Can I check a specific page rather than the domain?** Yes. Every input URL is checked as given, after following redirects.

**What happens on a timeout?** The site gets an `error` row saying "timed out" and isn't charged. Raise `timeoutSecs` for slow sites.

**Do you store my URLs?** Only in your own Apify run storage. With monitoring on, each URL's technology list is kept in a named key-value store in your account (`techstack-monitor-<name>`), so the next run can compare against it.

# Actor input Schema

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

Pages or domains to check, one per line. Any page works (e.g. a pricing or checkout page), not just the homepage. Bare domains get https://.

## `renderJs` (type: `boolean`):

Load each page in Chrome and also read JavaScript globals, the rendered DOM and background requests. On allbirds.com this found 25 website technologies instead of 10 (frameworks, analytics and ad tags, widgets). Slower, and costs an extra $0.003 per site. Off = fast HTTP check.

## `checkDns` (type: `boolean`):

Read MX, TXT, NS and SOA records of the domain: shows the email provider (Google Workspace, Microsoft 365), DNS host, and SaaS tools that left domain-verification records.

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

One row per technology (best for filtering and spreadsheets) or one row per site with a technologies list (best for CRM enrichment).

## `categories` (type: `array`):

Keep only technologies in these categories, e.g. CMS, Ecommerce, Analytics, Payment processors, CDN, Webmail, JavaScript frameworks, Marketing automation, Live chat. Empty = all.

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

Drop weak, single-hint matches below this confidence. 50 is a good balance; 0 shows everything.

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

Remember each site's technologies between runs and output only what was added or removed since the last run (the first run outputs a baseline). Use it with a weekly schedule to get alerts when a prospect or competitor switches platforms.

## `monitorName` (type: `string`):

Keeps separate histories for separate lists, e.g. "prospects" or "competitors". Use the same name on every scheduled run.

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

How many sites to check at once. Default: 10 in HTTP mode (up to 50), 3 in browser mode (up to 5; give browser mode 2 GB or more memory for 3+).

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

Give up on a site after this many seconds (counted as an error, not charged).

## `selfCheck` (type: `boolean`):

Runs the maintainer's health check instead of a normal lookup.

## Actor input object example

```json
{
  "urls": [
    "https://www.allbirds.com",
    "wordpress.org",
    "https://www.bbc.com"
  ],
  "renderJs": false,
  "checkDns": true,
  "outputFormat": "technologies",
  "minConfidence": 50,
  "monitorChanges": false,
  "monitorName": "default",
  "timeoutSecs": 25,
  "selfCheck": false
}
```

# Actor output Schema

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,postatelier/website-tech-stack"
        }
    }
}
```

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/yMGxyyxGi09iWNQ6a/builds/cDUauoNgzeoisjcut/openapi.json
