# Tech Stack Detector - CMS & Technology Lookup (`luxe_tasks/tech-stack-detector`) Actor

Website tech stack lookup: CMS, frameworks, analytics, CDN and server, with evidence. From $2/1,000 site scans + $0.005 start fee per run.

- **URL**: https://apify.com/luxe_tasks/tech-stack-detector.md
- **Developed by:** [Luxe Tasks](https://apify.com/luxe_tasks) (community)
- **Categories:** Developer tools, 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 site scans

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

## Tech Stack Detector - CMS & Technology Lookup

Find out which CMS, JavaScript framework, analytics and tag manager, CDN, hosting and web server a public website uses. Give it a list of URLs. For each site you get the detected technologies with a **category**, a **version when the site shows one**, a **confidence (0-100)** and the **evidence** (which header, tag or file matched). The evidence never contains page content or cookie values.

**Cost:** from $2 per 1,000 site scans, plus a $0.005 start fee per run. A site counts as a scan only when at least one technology is detected. Sites with nothing detected and failed sites have no per-site charge, but the run's start fee still applies.

**Try it:** the input is prefilled with one URL. Press Start and open the dataset's technologies view.

### How to find what CMS or tech stack a website uses

1. Paste the website URLs into **Website URLs** (one per line).
2. Leave **Minimum confidence (0-100)** at 50, or lower it to see weaker findings (more false positives).
3. Optional: set **Pages per site** to 2-3 to also read up to 2 linked pages on the same site (still one charge per site).
4. Press **Start**. Each dataset record lists `technologyNames`, `categories` and, per technology, the evidence.
5. Export as CSV, JSON or Excel from the dataset, or call it from code via the API tab.

### Example input and output

- Input: `{ "urls": ["https://www.python.org/"] }` (the live Store example input; the input form's prefill in build 0.1.5 is `https://example.com/`).
- Output:

```json
{"index": 0, "url": "https://www.python.org/", "status": "success", "ok": true, "key": null, "link": null, "httpStatus": 200, "charged": true, "chargedEvents": 1, "chargeEvent": "site-scan", "attempts": 1, "error": null, "robots": {"checked": true, "allowed": true, "overridden": false, "detail": "robots.txt HTTP 200"}, "startedAt": "2026-10-26T04:44:30.009Z", "timingMs": 340, "finalUrl": "https://www.python.org/", "redirects": 0, "pagesScanned": 1, "requests": 1, "technologyCount": 3, "technologyNames": ["jQuery", "nginx", "Varnish"], "categories": ["Cache", "JavaScript library", "Web server"], "truncated": false,
  "technologies": [
    {"name": "jQuery", "category": "JavaScript library", "version": "1.8.2", "confidence": 99, "evidence": [{"source": "script-src", "pattern": "/\\/jquery\\/(\\d+(?:\\.\\d+){0,3})\\/jquery(?:\\.slim)?(?:\\.min)?\\.js/i"}]},
    {"name": "nginx", "category": "Web server", "version": null, "confidence": 95, "evidence": [{"source": "header", "name": "server", "pattern": "/^nginx(?:\\/(\\d+(?:\\.\\d+){0,3}))?(?:\\s|$)/i"}]},
    {"name": "Varnish", "category": "Cache", "version": null, "confidence": 85, "evidence": [{"source": "header", "name": "via", "pattern": "/\\bvarnish\\b/i"}]}
  ] }
```

### How it detects (and what it does NOT do)

- Give it a list of website URLs; for each site it reads **one page** (the URL you give) and reports the technologies it can see: CMS and site
  builders (WordPress, Shopify, Drupal, Wix, ...), JavaScript frameworks (React, Next.js, Vue, Angular, ...), analytics and tag managers,
  CDN / hosting and the web server. Each finding has a **category**, a **version when the site shows one**, a **confidence (0-100)** and the
  **evidence**: which rule matched (for example: header `server` matched `/^nginx(?:\/(\d+(?:\.\d+){0,3}))?(?:\s|$)/i`). The evidence never contains
  page content or cookie values.
- **Response headers** (`server`, `x-powered-by`, CDN ids, ...), **HTML markup** (`<meta name="generator">`, `<script src>`, `<link href>`,
  framework attributes such as `ng-version`), small **inline-script** markers, and **cookie names** set by the site (names only; values are never read).
- Text, headings, HTML comments, `<a href>` links, code samples and JSON-LD are **ignored**, so a blog post *about* WordPress is not reported as WordPress.
- 38 technologies in 18 categories, from our own hand-written ruleset (no third-party fingerprint database).
- It does **not** execute JavaScript, does not log in, sends no cookies, and does not look at pages other than the one you give unless you ask (below).

### Honest limitations

- **Heuristic.** Results are evidence-based guesses. Expect **false negatives** (a site that strips its generator tag, or a bundled React app with
  no visible marker, shows nothing) and occasional **false positives** (a site's own file named `react.js` looks like React).
- On our synthetic test set the detector scored precision 1.00 / recall 1.00 on 25 fixtures we wrote the rules against, and **precision 0.93 /
  recall 0.87 on 14 "blind" fixtures written afterwards** (first blind run, before one fix: 0.87 / 0.87). Real-world accuracy is not measured yet.
- Only what the server sends without JavaScript is visible. Single-page apps that render everything client-side reveal less.
- Versions are shown only when the page exposes them (generator tag, file name, `?ver=`); they can be stale or deliberately faked.
- Sites behind a bot wall, CAPTCHA or rate limit are **not** analysed (see below), and have no per-site charge.

### Input

- `urls` (required): pages to analyse, e.g. `https://example.com/`. URLs with a user name or password (`user:pass@host`) are refused; query strings are never logged.
- `minConfidence` (0-100, default 50): leave out weaker findings.
- `maxPagesPerSite` (1-3, default **1**): 2-3 also reads up to 2 same-origin pages linked from the first page only (depth 1, GET, never followed further; no query strings, no login, account, cart, checkout, admin, search, profile or upload pages). Still one charge per site.
- `probeWellKnownPaths` (default off): also requests up to 3 fixed paths that confirm a CMS (`/wp-json/`, `/core/misc/drupal.js`, `/media/system/js/core.js`), only where robots.txt allows (fixed list; no login or admin paths are ever requested).
- `includeEvidence` (default on), `timeoutSecs` (default 15), `maxConcurrency` (default 3, max 5).
- Owner-only kit fields `ignoreRobotsTxt` / `overrideDomainStop` require an attestation that you own or control the one domain named; small runs only.

### Output

One dataset record per input URL (in `url` and `finalUrl` a query string is shown as `?[redacted]`): `index, url, status, ok, charged, chargedEvents, httpStatus, error, robots, timingMs` plus
`finalUrl`, `redirects`, `pagesScanned`, `requests`, `technologyCount`, `technologyNames`, `categories`,
`technologies: [{name, category, version, confidence, evidence: [{source, name?, pattern}]}]`, `probes` (if enabled: per path only `confirmed CMS X` or `not confirmed`), `truncated`.
Statuses with no per-site charge: `no_technologies_detected`, `not_html`, `empty_page`, `http_error` (404 etc.), `http_5xx`, `login_required` (401),
`robots_disallowed`, `blocked_or_captcha`, `rate_limited`, `skipped_blocked`, `ssrf_blocked`, `invalid_input`, `timeout`, `network_error`, `budget_reached`.
`RUN_SUMMARY` in the key-value store has counts, charged events, stopped domains and request totals.

### Pricing

**Pricing: $2 per 1,000 site scans; plus a $0.005 start fee per run. Failed scans have no per-result charge; the per-run start fee still applies.**

- Pay-per-event **`site-scan`**: one event per site on which at least one technology was detected. Sites where nothing is detected, and every failure status above, add no per-result charge. Extra pages and path probes do not add events.
- **What you pay even when every URL fails:** the $0.005 start fee. Apify charges it through its synthetic `apify-actor-start` event on every run: "Up to and including 1 GB of RAM, the event is charged once. Then, it's charged once for each extra GB of memory" ([Apify docs](https://docs.apify.com/platform/actors/publishing/monetize/pay-per-event#synthetic-start-event)). This Actor's memory is limited to 512-1024 MB, so the start fee is always exactly $0.005 per run.
- The synthetic `apify-default-dataset-item` event is not used (the Actor refuses to run if it is priced). A maximum total charge set on the run is respected.

### Politeness and safety (kit defaults; may only be tightened)

- robots.txt honoured for every request (input URL, every redirect hop, extra pages and path probes).
- A robots.txt that returns 4xx (except 429) means no restrictions; a 429 is treated as a rate limit and stops the domain; 5xx or an unreachable robots.txt means nothing is fetched.
- At most ~1 request per second per registered domain; fixed honest User-Agent `TechStackActor/0.1.0 (+https://apify.com/luxe_tasks/tech-stack-detector; honours robots.txt)`; no proxy rotation, no evasion.
- A 403, 429 (Retry-After over 60 s, or still 429 after one retry), or CAPTCHA/bot-wall page on any request for a site stops the whole registered
  domain for the rest of the run (and after a restart); later items are skipped, with no per-site charge. 404 and 5xx skip only that URL (5xx is not retried).
- Private, loopback, link-local and cloud-metadata addresses are refused, re-checked after DNS and on every redirect.
- No logins, cookies, headers, proxies or session tokens can be supplied (the run refuses such input). Response bodies are read up to 768 KB.

### Acceptable use

> **Acceptable use.** This Actor processes **publicly accessible web pages that you choose**. By running it you confirm that:
>
> 1. You will submit only **public URLs** that you have the right or permission to process, and you will use the output lawfully.
> 2. You will **not** submit pages behind a login, paywall, or other access control, and the Actor does not accept passwords, cookies or session tokens.
> 3. You will **not** use it to get around CAPTCHAs, rate limits, IP blocks, or any other technical measure a site uses to restrict access.
> 4. You will comply with the target site's terms of use and robots.txt, and with all applicable law, including copyright and data-protection law (GDPR, CCPA and similar). You are the controller of any personal data in your results.
> 5. You will **not** process, store or distribute illegal content, or use the Actor to harass, stalk, or profile individuals.
> 6. You will not use the Actor for vulnerability scanning, attack reconnaissance or security testing of sites you do not own or have written permission to test. The Actor reports technologies, not vulnerabilities.
> 7. You will keep request volumes reasonable. The Actor enforces per-domain rate limits and identifies itself in its User-Agent.
>    The Actor may refuse or skip URLs that violate these rules. Report problems through the Actor's **Issues** tab.

### Privacy

This Actor stores no page content; records hold technology names, rule patterns and URLs you supplied.

> "Results are written only to your run's default Apify storage. The developer does not copy, view or retain them. If your results contain
> personal data, you are the controller and the developer acts only as your processor under Apify's Standard Actor Contract. Apify's retention settings apply."

### Third-party software

The detection rules are our own; no fingerprint database is used. Runtime dependencies (apify, robots-parser, tldts) are listed in NOTICE with their licences.
Technology names are trademarks of their respective owners and are used only to identify detected technologies; no affiliation or endorsement is implied.

### Runtime

Node 24 (`engines`, `.nvmrc`, Dockerfile `apify/actor-node:24`). Memory: default 1024 MB, allowed 512-1024 MB (the work is network-bound and rate-limited to about 1 request per second per domain, so more memory does not make a run faster). Tests: `npm test`. Local reliability run:
`python3 test/reliability/tech_runs.py`; benchmark: `python3 bench/tech_bench.py` (fixture servers on 127.0.0.1 only).

### FAQ

**How do I find what CMS a website uses?** Add the site's URL to the input and run Tech Stack Detector. Records with a technology in the `CMS` or `Site builder` category name it, with the evidence that matched.

**How much does a tech stack scan cost?** From $2 per 1,000 site scans plus a $0.005 start fee per run, so 1,000 sites with at least one detected technology, scanned in one run, cost $2.005 in total.

**How accurate is it?** Tech Stack Detector is heuristic: it reports what the site exposes without running JavaScript, so expect some false negatives and occasional false positives. Accuracy on real websites has not been measured yet. See "Honest limitations".

**Does it run JavaScript or log in?** No. Tech Stack Detector reads the HTML and headers the server sends, sends no cookies and never logs in.

**Does it scan for vulnerabilities?** No. It reports technologies, not vulnerabilities. Security testing of sites you don't own is not allowed (Acceptable use, item 6).

### More tools from Luxe Tasks

Want a picture of each site you scanned? Website Screenshot & URL to PDF Converter takes the same URL list.

| Tool | What it does |
|---|---|
| [Website Screenshot & URL to PDF Converter](https://apify.com/luxe_tasks/screenshot-pdf) | PNG/JPEG screenshots (viewport or full page) and PDFs of public web pages you choose. |
| [OCR Text Extractor](https://apify.com/luxe_tasks/ocr-text-extractor) | Extracts English text from scanned PDFs, PNG and JPEG images with Tesseract OCR that runs inside the Actor. |
| [HTTP API Request Runner](https://apify.com/luxe_tasks/http-runner) | Calls APIs you are authorized to use, with your own key kept in an encrypted secret input field. |

Each tool is priced per result plus a per-run start fee; see that tool's Pricing section.

Developer: Luxe_tasks.

Contact: https://apify.com/luxe_tasks · Issues tab on the actor page.

# Actor input Schema

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

Pages to analyse, e.g. https://example.com/. One page per URL by default. URLs with a user name or password (user:pass@host) are refused; query strings are never logged.

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

Technologies below this confidence are left out. Lower = more findings and more false positives.

## `maxPagesPerSite` (type: `integer`):

1 = only the URL you give (default). 2-3 = also up to 2 same-origin pages linked from that first page only (depth 1; no query strings; no login, account, cart, checkout, admin, search, profile or upload pages). Still one charge per site.

## `probeWellKnownPaths` (type: `boolean`):

Also request 3 fixed paths (/wp-json/, /core/misc/drupal.js, /media/system/js/core.js) that confirm a CMS. Only where robots.txt allows; no login or admin paths are ever requested.

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

For each technology, list which rule matched (e.g. header "server" matched /^nginx/). Never includes page content or cookie values.

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

Per-request timeout.

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

Items processed in parallel (each site still gets at most 1 request per second).

## `ignoreRobotsTxt` (type: `boolean`):

Only for a site you own or control; requires the attestation below; small runs only.

## `overrideDomainStop` (type: `boolean`):

Only for a site you own or control; requires the attestation below.

## `robotsOwnershipAttestation` (type: `string`):

A statement that you own or control the one registered domain in the input, naming it.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/"
  ],
  "minConfidence": 50,
  "maxPagesPerSite": 1,
  "probeWellKnownPaths": false,
  "includeEvidence": true,
  "timeoutSecs": 15,
  "maxConcurrency": 3,
  "ignoreRobotsTxt": false,
  "overrideDomainStop": false,
  "robotsOwnershipAttestation": ""
}
```

# Actor output Schema

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

Default dataset, one record per input URL, shown in the 'Results' view (a 'Technologies (with evidence)' view is also available): index, url (query string redacted), status (success or the reason, e.g. no_technologies_detected, robots_disallowed, rate_limited, skipped_blocked, http_error), ok, httpStatus, finalUrl, redirects, pagesScanned, requests, technologyCount, technologyNames, categories, technologies (name, category, version, confidence 0-100 and the matching rules as evidence), probes (only when well-known path checks are on), pagesSkipped, truncated, robots (checked, allowed, detail), attempts, charged, chargedEvents, chargeEvent (site-scan), startedAt, timingMs and error. Only successful scans with at least one technology are charged.

## `runSummary` (type: `string`):

RUN_SUMMARY record in the default key-value store: total URLs, counts by status, charged events, budget reached, domains stopped for the rest of the run (with the reason) and network totals. The store also holds INPUT and STATE (progress record used on restart).

# 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://example.com/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("luxe_tasks/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 = { "urls": ["https://example.com/"] }

# Run the Actor and wait for it to finish
run = client.actor("luxe_tasks/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 '{
  "urls": [
    "https://example.com/"
  ]
}' |
apify call luxe_tasks/tech-stack-detector --silent --output-dataset

```

## MCP server setup

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