# Tech Stack Audit — Website Technology Detector with Evidence (`power_on/tech-stack-audit`) Actor

Detect a website's tech stack with a real browser: CMS, ecommerce, frameworks, CDN, analytics and pixels. Returns the version and how old it is, which trackers really fire, security headers, cookies set before consent — and the evidence for every detection.

- **URL**: https://apify.com/power\_on/tech-stack-audit.md
- **Developed by:** [Power On Labs](https://apify.com/power_on) (community)
- **Categories:** Developer tools, SEO tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $35.00 / 1,000 site audits

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Tech Stack Audit — website technology detector that shows its work

Point it at a website and get back its technology stack: CMS, ecommerce platform,
JavaScript framework, web server, CDN, analytics, advertising pixels, consent
platform, payment provider, and about 280 technologies in all.

Then it answers the three questions a plain technology list leaves open:

1. **Which version, and how old is it?** Not "WordPress", but *WordPress 6.4.3, on
   a line that shipped 2.8 years ago, 6.8 is newer*.
2. **What actually runs?** Not "the Meta Pixel tag is in the HTML", but *the Meta
   Pixel really fired a network request on page load* — or didn't.
3. **Is the site's own house in order?** Security headers present, missing or
   weak, and which tracking cookies were written **before anyone consented**.

And for every single detection, **the evidence**: the exact header, script URL,
cookie, meta tag, JavaScript global or DOM node that identified it.

A BuiltWith and Wappalyzer alternative that runs a real browser instead of
reading the HTML and guessing.

***

### Why the evidence matters

Every competing tool returns a list of names. A name you cannot check is a name
you have to trust, and technology detection is guesswork often enough that trust
is the wrong setting. So every technology in the output carries what proved it:

```json
{
  "name": "jQuery",
  "version": "3.6.0",
  "confidence": 100,
  "activeAtRuntime": true,
  "evidence": [
    { "type": "javascript global", "source": "window.jQuery — jQuery.fn.jquery = 3.6.0", "matched": "window.jQuery" },
    { "type": "resource", "source": "https://www.php.net/js/vendor/jquery-3.6.0.min.js", "matched": "jquery-3.6.0", "seenIn": "markup and network" }
  ],
  "versionAge": {
    "releasedOn": "2021-03-02",
    "ageDays": 2014,
    "latestKnown": "3.7",
    "outdated": true,
    "verdict": "jQuery 3.6.0 is on the current major line but line 3.6 shipped 5.5 years ago; 3.7 is newer."
  }
}
```

You can verify that line yourself in ten seconds. That is the whole idea.

### What a real browser buys you

Most technology lookups fetch the HTML and read the headers. That is fast and
cheap, and it cannot see:

| Question | HTML-only scanner | This Actor |
|---|---|---|
| Is the Meta Pixel on this page? | It's in the markup | It's in the markup **and it fired** — or it's in the markup and never fired |
| Which third parties get visitor data? | The ones written into the page | Every domain the browser really contacted — 22 on a typical news site |
| Which cookies are set before consent? | Cannot know: no cookies are set without executing the page | The full list, classified, with lifetimes |
| Which tags a consent banner is actually blocking | Invisible | Reported as *declared but not requested* |

The audit **clicks nothing** — no banner is accepted, no button is pressed. That
constraint is what lets the privacy section say "before consent" and mean it.

### Sample output, one row per site

```jsonc
{
  "url": "https://example.com",
  "reachedRealPage": true,
  "statusCode": 200,
  "technologyCount": 11,
  "technologyNames": ["WordPress", "jQuery", "Google Tag Manager", "Meta Pixel", "..."],
  "categories": { "CMS": ["WordPress"], "Advertising": ["Meta Pixel"], "...": [] },

  "cms":         { "name": "WordPress", "version": "6.4.3", "outdated": true, "verdict": "…2.8 years ago…" },
  "jsFramework": { "name": "React", "version": null, "outdated": null, "verdict": null },
  "webServer":   { "name": "nginx", "version": "1.18.0", "outdated": true, "verdict": "…" },

  "outdatedTechnologies": [ { "name": "PHP", "version": "7.4.33", "endOfLife": true, "verdict": "…no longer receives security fixes…" } ],
  "outdatedCount": 2,
  "endOfLifeCount": 1,

  "runtime": {
    "requestCount": 148,
    "thirdPartyDomainCount": 22,
    "technologiesActiveAtRuntime": ["Google Tag Manager", "Meta Pixel"],
    "technologiesDeclaredButNotRequested": ["Comscore"],
    "trackersFiring": ["Google Tag Manager", "Meta Pixel"]
  },

  "security": {
    "grade": "D", "score": 47, "https": true,
    "headersMissing": [ { "label": "HSTS", "cost": 18, "why": "Without HSTS a first visit over http can be intercepted…" } ],
    "headersWeak":    [ { "label": "Content-Security-Policy", "warning": "Policy present but weak: allows 'unsafe-inline', allows any host (*) for scripts." } ],
    "softwareDisclosure": [ { "header": "x-powered-by", "value": "PHP/7.4.33", "versionLeak": true } ],
    "scoring": "Starts at 100. Each missing header costs its listed weight…"
  },

  "privacy": {
    "consentPlatformDetected": true,
    "consentPlatforms": ["OneTrust"],
    "trackersActiveBeforeConsent": ["Google Tag Manager"],
    "trackingCookiesBeforeConsent": [ { "name": "_fbp", "tracker": "Meta Pixel", "expiresInDays": 89 } ],
    "finding": "Consent platform present (OneTrust), but 1 tracker(s) and 1 tracking cookie(s) were already active before any choice was made."
  },

  "technologies": [ /* every detection, with its evidence */ ],
  "thirdPartyDomains": ["connect.facebook.net", "…"]
}
```

### What it's for

- **Lead qualification** — find every prospect running Shopify, or WooCommerce,
  or an end-of-life PHP.
- **Competitive research** — what a competitor's stack is, and how current.
- **Agency pitches** — walk in with "your WordPress is two years old, your PHP is
  end-of-life, and you have no CSP", each line backed by the header that proves it.
- **Privacy and GDPR review** — which trackers fire before consent, on your sites
  or a supplier's.
- **Security triage at scale** — a grade and a missing-header list per domain.

### Input

Paste a list of websites. Bare domains are fine — `example.com` becomes
`https://example.com`.

| Field | Default | What it does |
|---|---|---|
| `urls` | `["https://example.com"]` | Websites to audit, one row of output per site |
| `device` | `desktop` | `desktop` or `mobile` — some sites serve a different stack to phones |
| `minConfidence` | `50` | Raise to 80 for detections proven by a decisive signal only |
| `includeEvidence` | `true` | Attach the proof to every detection |
| `includeCookies` | `true` | Full cookie list, with lifetimes and tracker attribution |
| `includeThirdPartyDomains` | `true` | Every external domain really contacted |
| `includeRequestLog` | `false` | Every network request, for auditing the evidence yourself |
| `waitUntil` | `load` | `load`, `domcontentloaded` or `networkidle` |
| `settleMs` | `1500` | Extra wait so late-firing tags are still caught |
| `timeoutSecs` | `45` | Per site |
| `concurrency` | `4` | Sites audited in parallel |
| `retries` | `1` | Retries before a site is recorded as failed |
| `locale` / `timezone` | `en-US` / — | Changes which consent banner and regional tags load |

### Pricing

**$0.05 per site audited** — half what the two leading tech-stack Actors charge for a
lookup that never opens a browser. No start fee, no subscription, no API key.
Higher Apify plans pay less, down to $0.035.

**You are not charged for:**

- sites that failed to load — DNS failure, timeout, connection refused, TLS error;
- anything that answered 4xx or 5xx instead of a page;
- **sites behind a bot wall**, where the real page was never reached (see below).

Every row says which it was, in a `charged` field.

### Read before you buy — two honest limits

**1. Bot walls are reported, not defeated.** Some sites answer a bot-detection
challenge instead of their page: Cloudflare, Fastly, DataDome, Imperva, Akamai,
AWS WAF, PerimeterX. This Actor recognises the wall, sets `reachedRealPage:
false`, names the vendor, and **does not charge you**:

```json
{ "reachedRealPage": false,
  "blockedBy": { "vendor": "Fastly", "marker": "Fastly client challenge",
                 "explanation": "…the technologies below describe the wall, not the site…" },
  "charged": false }
```

Getting past those walls reliably needs paid residential proxies. This Actor does
not use them. A scanner that meets a challenge page and cheerfully reports
"Cloudflare, nginx" has audited the wall, not the site, and given you no way to
tell — which is worse than saying so. If you have your own proxies, pass them in
the `proxyConfiguration` field.

**2. The version table has a compile date, and it ships with every result.**
Version age is judged against a bundled table of release lines and dates; the
date it was compiled is in every row as `versionTableCompiledOn`. If a site runs
something newer than the table knows, it is reported as *newer than the table*,
never as outdated. Where a version cannot be established the output says
`ageDays: null` and `basis: "unknown"` rather than inventing a verdict.

### Coverage

About 280 technologies across 42 categories: CMS, ecommerce, headless CMS,
website builders, page builders, JavaScript frameworks and libraries, UI
frameworks, static site generators, build tools, analytics, session recording,
tag managers, A/B testing, advertising and pixels, marketing automation, email
marketing, customer support and chat, consent management, CDN, hosting, reverse
proxies, web servers, programming languages, web frameworks, databases, security
and bot management, search, payments, reviews, video, media, maps, fonts,
authentication, backends, forms, scheduling, comments, monitoring and SEO.

Detection rules are written and maintained for this Actor. Version age is
tracked for the technologies that realistically expose a version: WordPress,
Drupal, Joomla, TYPO3, PHP, jQuery, Bootstrap, nginx, Apache, IIS, React,
Next.js, Vue, Angular, AngularJS, Magento, PrestaShop, Shopware, Django,
Laravel, Symfony, Rails, Ember, Font Awesome, Moment.js, Lodash, D3, GSAP,
Video.js, Swiper, Alpine.js and htmx.

### Use it from code

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('power_on/tech-stack-audit').call({
    urls: ['https://example.com', 'https://another-site.com'],
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const site of items) {
    if (!site.ok || !site.reachedRealPage) continue;
    console.log(site.url, site.technologyNames.join(', '));
    for (const old of site.outdatedTechnologies) console.log('  ⚠', old.verdict);
}
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run = client.actor("power_on/tech-stack-audit").call(run_input={
    "urls": ["https://example.com", "https://another-site.com"],
})

for site in client.dataset(run["defaultDatasetId"]).iterate_items():
    if not site.get("ok") or not site.get("reachedRealPage"):
        continue
    print(site["url"], site["technologyNames"])
    for old in site["outdatedTechnologies"]:
        print("  !", old["verdict"])
```

### Also from Power On Labs

- [Website Screenshot & PDF Generator](https://apify.com/power_on/screenshot-url-pdf) —
  full-page, viewport or element captures and page-to-PDF, with cookie banners removed.
- [PDF to JSON Extractor](https://apify.com/power_on/pdf-to-json-extractor) —
  tables as real rows and columns, true reading order, key fields.

# Actor input Schema

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

Websites to audit — one per line. A bare domain like example.com works; https:// is added for you. One dataset row is produced per site.

## `device` (type: `string`):

Which browser to pretend to be. Some sites serve a different stack to phones, so a mobile audit can return different technologies.

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

Drop detections below this confidence score (0-100). Raise it to 80 if you only want the technologies proven by a decisive signal such as a header or a JavaScript global.

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

Attach to every detected technology the exact header, resource URL, cookie, meta tag or JavaScript global that identified it. This is what makes a result checkable; turn it off only if you want smaller rows.

## `includeCookies` (type: `boolean`):

List every cookie set during the load, with its domain, lifetime and whether it belongs to a known tracker. The tracking ones are always reported; this adds the rest.

## `includeThirdPartyDomains` (type: `boolean`):

List every external domain the page really contacted while loading.

## `includeRequestLog` (type: `boolean`):

Every network request the page made, with its resource type. Verbose — useful when you want to audit the evidence yourself.

## `waitUntil` (type: `string`):

How far to let the page load before reading it. "Page load" suits almost everything; "DOM ready" is faster but can miss late-firing tags.

## `settleMs` (type: `integer`):

Extra wait after loading, so tag managers and analytics that fire late are still caught. Lower it to go faster, raise it for heavy sites.

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

How long to give a single site before giving up on it and recording the reason.

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

How many sites to audit at the same time. Capped by the run's memory — about one page per gigabyte — because more parallel pages than the container can hold makes the run slower, not faster.

## `retries` (type: `integer`):

How many times to retry a site that failed to load before recording it as an error.

## `locale` (type: `string`):

Browser language sent with the request, e.g. en-US, it-IT, de-DE. Changes which consent banner and which regional tags a site loads.

## `timezone` (type: `string`):

IANA timezone name, e.g. Europe/Rome. Leave empty to use the server default.

## `extraHeaders` (type: `object`):

Extra HTTP headers sent with every request, as a JSON object.

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

Optional proxy, for geo-specific results or sites that block datacenter traffic. Not needed for a normal audit.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com"
  ],
  "device": "desktop",
  "minConfidence": 50,
  "includeEvidence": true,
  "includeCookies": true,
  "includeThirdPartyDomains": true,
  "includeRequestLog": false,
  "waitUntil": "load",
  "settleMs": 1500,
  "timeoutSecs": 45,
  "concurrency": 4,
  "retries": 1,
  "locale": "en-US",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# 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 = {
    "urls": [
        "https://apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("power_on/tech-stack-audit").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://apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("power_on/tech-stack-audit").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://apify.com"
  ]
}' |
apify call power_on/tech-stack-audit --silent --output-dataset

```

## MCP server setup

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

```

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/jQR5Pl9x4clv8VKHC/builds/DuiTU0ksDAHSp6J75/openapi.json
