# Tech Stack Detector – Website Technologies (`sfoln/tech-stack-detector`) Actor

Detect the tech stack of any website: CMS, e-commerce platform, analytics, tag managers, ad pixels, chat, payments, CDN, hosting and web server, plus SaaS verified in DNS (Google Workspace, HubSpot, Zendesk…). Live detection, one JSON record per site. $2 per 1,000 websites.

- **URL**: https://apify.com/sfoln/tech-stack-detector.md
- **Developed by:** [SFOLN LLC](https://apify.com/sfoln) (community)
- **Categories:** Lead generation, SEO tools, 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 website tech stacks

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

Find out **what any website is built with**: give it domains or URLs and get one JSON record per site with every detected technology (name, categories, version, confidence and the evidence it was found by) plus headline fields: **CMS, e-commerce platform, analytics, tag managers, ad pixels, live chat, payment processors, CDN, hosting, web server and frameworks**. On top of the page itself it reads the TLS certificate and **DNS records**, so it also finds SaaS that never shows up in the HTML: the mailbox provider (Google Workspace, Microsoft 365), e-mail senders from SPF (SendGrid, Mailchimp, Amazon SES), and services verified in TXT records or behind subdomains (HubSpot, Zendesk, Stripe, Atlassian…). Built for sales prospecting by technology ("Shopify stores using Klaviyo"), competitive research, lead enrichment and AI agents.

- **What you get:** `technologies[]`, `byCategory`, `cms`, `ecommercePlatform`, `analytics`, `advertising`, `cdn`, `hosting`, `emailProvider`, `saasVerifiedByDns`.
- **Price:** $2 per 1,000 websites (`tech-stack`), no subscription; blocked, missing and unreachable sites are free.
- **Live, not a crawl database:** every run loads the site now, from more than 3,500 fingerprints (Wappalyzer 6.10.54, MIT) plus our own DNS and tag-snippet rules.
- **Works everywhere:** API, MCP for AI agents, Make, Zapier, n8n, schedules and webhooks.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `urls` | string\[] | – | Domains (`apify.com`) or page URLs, one per line. A domain is analysed from its homepage (https://, falling back to http://). Duplicates are removed. |
| `maxPages` | integer | 1 | `2` also analyses one more page of the same site (pricing, shop, blog, contact…), which catches checkout, payment and chat tools. A second page that redirects to another site is skipped with a warning. |
| `includeDnsSaaS` | boolean | `true` | Read TXT, MX, SPF, NS and the CNAMEs of common subdomains for SaaS seen only in DNS. |
| `categories` | string\[] | – | Keep only these categories (e.g. `CMS`, `Ecommerce`, `Analytics`, `Advertising`, `CDN`). Empty = all. |
| `minConfidence` | integer | 0 | Drop technologies detected with a lower confidence (0–100). |
| `maxItems` | integer | 1000 | Safety cap for this run; websites beyond it are ignored and counted as `inputItemsDropped` in the `OUTPUT` record. |
| `concurrency` | integer | 5 | Websites analysed in parallel (1–10). |
| `includeRaw` | boolean | `false` | Attach the analysed pages' response headers and the DNS records used under `raw`. |

```json
{
  "urls": ["apify.com", "https://www.allbirds.com/"],
  "maxPages": 2,
  "includeDnsSaaS": true
}
```

### Output

One dataset record per website (the `technologies` list is shortened here):

```json
{
  "input": "wordpress.org",
  "domain": "wordpress.org",
  "url": "https://wordpress.org/",
  "title": "Blog Tool, Publishing Platform, and CMS – WordPress.org",
  "language": "en-US",
  "technologyCount": 14,
  "technologyNames": ["WordPress", "C", "MySQL", "PHP", "Gutenberg", "Nginx", "Google Font API", "Google Tag Manager", "HSTS", "Priority Hints", "Open Graph", "RSS", "Google Search Console", "Help Scout"],
  "byCategory": { "CMS": ["WordPress"], "Blogs": ["WordPress"], "Web servers": ["Nginx"], "Tag managers": ["Google Tag Manager"], "Live chat": ["Help Scout"] },
  "cms": "WordPress",
  "ecommercePlatform": null,
  "analytics": [],
  "tagManagers": ["Google Tag Manager"],
  "advertising": [],
  "marketingAutomation": [],
  "crm": [],
  "liveChat": ["Help Scout"],
  "paymentProcessors": [],
  "emailProvider": null,
  "emailSenders": ["Help Scout"],
  "hosting": [],
  "cdn": [],
  "webServer": "Nginx",
  "javascriptFrameworks": [],
  "programmingLanguages": ["C", "PHP"],
  "saasVerifiedByDns": ["Google Search Console", "Help Scout"],
  "technologies": [
    { "name": "WordPress", "slug": "wordpress", "categories": ["CMS", "Blogs"], "version": "6.9", "confidence": 100, "evidence": ["meta", "scriptSrc", "html"], "website": "https://wordpress.org" },
    { "name": "PHP", "slug": "php", "categories": ["Programming languages"], "version": null, "confidence": 100, "evidence": ["implied"], "website": "http://php.net", "impliedBy": ["WordPress"] },
    { "name": "Help Scout", "slug": "help-scout", "categories": ["Issue trackers", "Live chat"], "version": null, "confidence": 100, "evidence": ["dns-spf"], "website": "https://www.helpscout.com" }
  ],
  "pagesAnalyzed": 2,
  "pages": [{ "url": "https://wordpress.org/", "status": 200 }, { "url": "https://wordpress.org/news/", "status": 200 }],
  "dnsChecked": true,
  "certificateIssuer": "Let's Encrypt",
  "status": "ok",
  "charged": true,
  "chargedEvent": "tech-stack",
  "source": "website+dns",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

| Field | Description |
| --- | --- |
| `technologies` | Every detection: `name`, `slug`, `categories`, `version` (when the page reveals it), `confidence` (0–100), `evidence` (`header`, `cookie`, `meta`, `scriptSrc`, `script`, `html`, `dom`, `url`, `cert`, `dns-txt`, `dns-mx`, `dns-spf`, `dns-ns`, `dns-cname`, or `implied` with `impliedBy`), `website`. |
| `technologyNames`, `technologyCount`, `byCategory` | The same list as names, its length, and grouped by category name. |
| `cms`, `ecommercePlatform`, `webServer` | The main CMS, shop platform and web server (what the page itself shows wins over DNS-only hits, e.g. a `blog.` subdomain). |
| `analytics`, `tagManagers`, `advertising`, `marketingAutomation`, `crm`, `liveChat`, `paymentProcessors`, `hosting`, `cdn`, `javascriptFrameworks`, `programmingLanguages` | Technologies per headline category. |
| `emailProvider`, `emailSenders` | Mailbox provider from the MX records; services allowed to send the domain's mail (SPF includes). |
| `saasVerifiedByDns` | Technologies seen in DNS: verification TXT tokens, MX, SPF, name servers and CNAMEs of `www.`, `help.`, `support.`, `status.`, `shop.`, `store.`, `blog.`, `docs.`, `jobs.`, `careers.`, `go.`, `info.`, `mail.`. |
| `pagesAnalyzed`, `pages`, `dnsChecked`, `certificateIssuer` | What was looked at for this record. |

`status` values:

| `status` | Meaning | Charged |
| --- | --- | --- |
| `ok` | The start page answered; technologies detected (the list may be empty after your filters) | `tech-stack` |
| `not_found` | The domain does not resolve (`domain_not_found`), the page is gone (`page_not_found`, HTTP 404/410) or the site refused the request (`site_blocked`, HTTP 401/403/429/503) | no |
| `error` | The site could not be reached (timeout, connection or TLS error) or answered another HTTP error | no |
| `skipped_invalid` | Not a public website URL or domain (IP addresses, local names, ports other than 80/443) | no |
| `skipped_budget` / `skipped_free_plan` | Your cost cap or the free-plan limit was reached | no |

The key-value store record `OUTPUT` holds the run summary (item counts and the events and USD charged).

### Pricing

| Event | Price | When charged |
| --- | --- | --- |
| `tech-stack` | **$0.002** per website ($2 / 1,000) | once per website whose start page answered |

There is no start fee and no charge for sites that block the request, do not exist or fail. Set **Maximum cost per run** to cap spend: when the cap is reached the remaining websites are returned with `status: "skipped_budget"` and are not charged. Accounts on the Apify free plan can process up to 10 websites per run; upgrade to any paid plan for full batches.

### Use with AI agents (MCP)

Use this Actor from Claude, ChatGPT, Cursor, VS Code or any other MCP client through the [Apify MCP server](https://mcp.apify.com). Add this server URL to your client and sign in with your Apify account when asked:

```text
https://mcp.apify.com?tools=sfoln/tech-stack-detector
```

To run the MCP server locally instead, start `npx -y @apify/actors-mcp-server --tools sfoln/tech-stack-detector` with your `APIFY_TOKEN` in the environment.

Example prompt:

> Which CMS, e-commerce platform, analytics tools and e-mail provider do apify.com and wordpress.org use?

Runs started by an agent are billed like any other run (see Pricing). Agents can cap what a run may spend with `maxTotalChargeUsd`.

### API usage

Run the Actor and get its dataset items in one call (the call waits up to 5 minutes for the run to finish):

```bash
curl -X POST "https://api.apify.com/v2/acts/sfoln~tech-stack-detector/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["apify.com"]}'
```

For large inputs, start the run asynchronously and download the dataset when it has finished:

```bash
## 1. Start a run: the response holds the run ID (data.id) and its dataset ID (data.defaultDatasetId)
curl -X POST "https://api.apify.com/v2/acts/sfoln~tech-stack-detector/runs" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["apify.com"]}'
## 2. Repeat until data.status is SUCCEEDED (each call waits up to 60 s)
curl "https://api.apify.com/v2/actor-runs/<runId>?waitForFinish=60" -H "Authorization: Bearer <APIFY_TOKEN>"
## 3. Download the results (format=json, csv or xlsx)
curl "https://api.apify.com/v2/datasets/<defaultDatasetId>/items?format=json" -H "Authorization: Bearer <APIFY_TOKEN>"
```

Python ([apify-client](https://pypi.org/project/apify-client/)):

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("sfoln/tech-stack-detector").call(run_input={"urls": ["apify.com"]})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

Instead of polling, you can attach a webhook to the run (see Integrations). The API tab of this page shows the same calls for JavaScript and the Apify CLI.

### Integrations

- **Make, Zapier and n8n:** run `sfoln/tech-stack-detector` with the Apify app (in n8n, the Apify node) and pass the dataset items to the next step of your workflow.
- **Google Sheets:** add the items to a sheet with the Google Sheets step of Make, Zapier or n8n, or download them as CSV or Excel from the run's Storage tab or the API (`format=csv` or `format=xlsx`).
- **Webhooks:** get a POST request when a run succeeds or fails: add a webhook in the Actor's Integrations tab, or pass `webhooks` when you start a run through the API.
- **Schedules:** run it hourly, daily or weekly with the same input from Apify Schedules; every run keeps its results in its own dataset.
- **More:** Slack, Google Drive, GitHub, LangChain, LlamaIndex and other [Apify integrations](https://docs.apify.com/platform/integrations).

### Related Actors

- [Website Audit – SEO, Security, SSL & DNS](https://apify.com/sfoln/website-audit)
- [Website Contact Details Scraper](https://apify.com/sfoln/company-contacts-finder)
- [Domain WHOIS & DNS Lookup](https://apify.com/sfoln/domain-intelligence)
- [Company Enrichment – Firmographics by Domain](https://apify.com/sfoln/company-enrichment)
- [SEO Competitor Analysis – Traffic & Keywords](https://apify.com/sfoln/seo-competitor-snapshot)

See [all our Actors](https://apify.com/sfoln) on Apify Store.

### FAQ

#### How much does it cost, and am I charged for failed items?

You pay per event at the prices under Pricing above, plus a run start fee of $0.00005 per GB of memory, with no subscription; platform usage is included. Errors and skipped items (`skipped_…` statuses) are never charged; a lookup that finds nothing is charged only where Pricing says so, and every record's `charged` field shows whether it was billed. Set **Maximum cost per run** to cap what a single run can spend. Some runs may be sponsored by a promotional trial: sponsored results are delivered without the usual event charge and are marked `sponsored` in the dataset; `apify-actor-start` may still be charged by Apify.

#### How fresh is the data?

Each run queries its source while it runs, so you get what the source returns at that moment. Source-specific details, such as how often a provider refreshes its own database, are under Limits & notes.

#### What are the limits?

Per-run caps (for example the maximum number of items) are listed under Input, source limits under Limits & notes, and the free-plan limit under Pricing. For big batches, start the run asynchronously (see API usage) and read the dataset when it has finished.

#### Can I schedule runs or get results by webhook?

Yes. Create a schedule in Apify Console to run it hourly, daily or weekly with the same input, and add a webhook to get a POST request when a run finishes (see Integrations).

#### What does this Actor record about my runs?

This Actor records operational metrics about each run (item counts, duration, charges, error codes and a pseudonymous hash of your Apify user ID) to monitor reliability and costs. It never stores your input or results.

#### How are technologies detected?

The Actor loads the page like a browser would (without running JavaScript) and matches its response headers, cookies, meta tags, script URLs, inline scripts and HTML against more than 3,500 technology fingerprints (Wappalyzer 6.10.54, MIT licence, plus our own rules for newer frameworks and tag snippets). It also reads the TLS certificate issuer and, with includeDnsSaaS, DNS records: verification tokens, mail servers, SPF senders, name servers and the CNAMEs of help., shop., status. and similar subdomains.

#### What can it miss?

Technologies that only load after JavaScript runs in a browser (some tag-manager payloads, single-page apps that inject everything client-side) and back-end tools that leave no trace in pages or DNS. Sites behind bot protection that refuse the request are returned with status not_found and are not charged.

#### How do I get help?

Open an issue in the Actor's Issues tab and include the link to the run, so we can reproduce the problem. Feature requests are welcome there too.

### Limits & notes

- Pages are fetched without running JavaScript: tools that only appear after scripts run in a browser can be missed (most tag snippets, pixels and frameworks leave traces in the HTML and are found).
- At most 2 pages per site, 20 seconds and 2 MB per page; only public http(s) hosts on ports 80 and 443 are contacted, and every redirect is checked.
- DNS comes from public DNS-over-HTTPS (Cloudflare); the TLS certificate is read with a direct handshake. Nothing is cached between runs.
- Fingerprints: Wappalyzer 6.10.54 (MIT licence) plus our own rules for DNS-verified SaaS, newer frameworks (Next.js App Router, Astro, SvelteKit, Remix) and inline tag snippets.
- Sites that sit behind strict bot protection may answer 403 or a challenge page; they are returned as `not_found` with `site_blocked` and are not charged.

# Actor input Schema

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

Domains (apify.com) or page URLs (https://www.apify.com/pricing), one per line. A domain is analysed from its homepage (https://, falling back to http://). Duplicates are removed. Accounts on the Apify free plan can process up to 10 websites per run.

## `maxPages` (type: `integer`):

1 = the start page only; 2 = also one more page of the same site (pricing, shop, blog, contact…) to catch technologies that load only there.

## `includeDnsSaaS` (type: `boolean`):

Read TXT verification tokens, MX, SPF, NS and the CNAMEs of common subdomains (help., shop., status., …) to find services that never appear in the HTML: Google Workspace, Microsoft 365, HubSpot, Zendesk, Stripe, SendGrid and more.

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

Keep only technologies in these categories, e.g. CMS, Ecommerce, Analytics, Advertising, CDN, Payment processors, Marketing automation (case-insensitive). Empty = every category.

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

Drop technologies detected with a confidence below this value (0–100; most detections are 100).

## `maxItems` (type: `integer`):

Safety cap on how many websites are processed in this run.

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

How many websites are analysed in parallel (1–10).

## `includeRaw` (type: `boolean`):

Attach the response headers of the analysed pages and the DNS records used under raw (larger output).

## Actor input object example

```json
{
  "urls": [
    "apify.com"
  ],
  "maxPages": 1,
  "includeDnsSaaS": true,
  "minConfidence": 0,
  "maxItems": 1000,
  "concurrency": 5,
  "includeRaw": 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": [
        "apify.com"
    ]
};

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

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

```

## MCP server setup

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