# Website Tech Stack Detector: CMS, Analytics, Hosting & Email (`folt/tech-stack-detector`) Actor

What any website runs on: CMS, e-commerce, analytics, frameworks, CDN, hosting and email provider. Up to 10,000 domains per run from a list, CSV, Google Sheet or another Actor's dataset. Filter by technology and track stack changes between runs. Failed or blocked sites are free.

- **URL**: https://apify.com/folt/tech-stack-detector.md
- **Developed by:** [Folt Labs](https://apify.com/folt) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 analyzed domains

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 Tech Stack Detector — CMS, Analytics, Hosting & Email Provider

Find out **what any website is built with**. Paste a list of domains and get back one clean record per site: CMS, e-commerce platform, analytics and tag managers, JavaScript frameworks, CDN, hosting, web server, payment providers, live chat, marketing tools, and the company's **email provider** (Google Workspace, Microsoft 365…) from DNS. It's built for **sales prospecting and market research**: qualify leads by their stack, size up a market, or find every company still on a platform you replace.

### Why use it

- 🔎 **Fingerprints for 7,600+ technologies**, about 6,700 of them detectable without a browser (community Wappalyzer fingerprints plus our own additions)
- 🧾 **One record per domain** with ready-to-filter fields: `cms`, `ecommercePlatform`, `analytics`, `tagManagers`, `jsFrameworks`, `cdn`, `hosting`, `emailProvider`…
- 📬 **Email & DNS intelligence**: mailbox provider, email security gateway, DNS provider, and SaaS tools that verified the domain (Atlassian, Stripe, HubSpot, Zoom…)
- 📥 **Import from anywhere**: paste domains, link a CSV / TXT file or a Google Sheet, or pick the dataset of another Actor run (for example Google Maps Scraper results)
- 🎯 **Filter by technology**: output only the sites that use Shopify, WordPress, HubSpot, any CMS…
- 🔔 **Track stack changes**: run the same list on a schedule and see which sites added or removed technologies since the last run
- ⚡ **Fast and cheap**: one request per site, no browser. 500 domains took about 2 minutes in our test, at default memory
- 💸 **Fair billing**: sites that are down, return an error, or show a bot-protection challenge are returned **free**, clearly marked
- 🛡️ **Safe by design**: only public websites are fetched; IP addresses, internal hostnames and private networks are refused

### Use cases

- **Sales lead qualification**: keep only prospects on Shopify, WordPress, HubSpot or Microsoft 365, or skip those already using a competitor
- **Competitor and market research**: measure platform share across a list of companies in a niche or country (the run's `SUMMARY` lists the most common technologies)
- **Agency prospecting**: find sites on outdated or DIY stacks (old jQuery, page builders, no tag manager) that could use a rebuild or migration
- **Security and asset inventory**: list the CMS, CDN, hosting, web server and email setup across your own company's domains
- **Local business prospecting**: take the websites from a Google Maps Scraper run and keep the businesses on WordPress or Wix
- **Switch signals**: monitor a list of prospects or customers weekly and catch the ones that moved to (or away from) a platform

### How to use

1. Add domains (`example.com`) or URLs (`https://example.com/shop`), one per line, up to 10,000 per run. Or import them from a file, a Google Sheet or a dataset (see below).
2. Keep **Check DNS** on to get the email provider, DNS provider and DNS-verified SaaS tools.
3. Turn on **Include evidence** to see which header, script, meta tag or DNS record each detection is based on.
4. Optionally limit the output to sites using certain technologies, or turn on change monitoring.
5. Run it, then filter the dataset or export it to CSV / Excel / Google Sheets.

#### Input example

```json
{
  "domains": ["allbirds.com", "wordpress.org", "https://nextjs.org", "example-store.com"],
  "checkDns": true,
  "includeEvidence": false,
  "maxConcurrency": 20
}
```

A bare domain means its homepage (we try `https://domain`, then `https://www.domain`, then `http://domain`). A URL with a path is analyzed as given. Duplicates (`example.com`, `https://www.example.com/`) are analyzed once.

#### Import from a file or a Google Sheet

```json
{
  "domains": [],
  "domainsFileUrl": "https://docs.google.com/spreadsheets/d/<your-sheet-id>/edit#gid=0"
}
```

- Works with a public link to a **CSV or TXT file** or a **Google Sheet** shared as *Anyone with the link can view* (the tab in the link is used).
- The column named `domain`, `website`, `url` or `homepage` is used, in that order of preference (a header such as *Company Website* or *Website URL* also works). Without one, the first column is used.
- Comma, semicolon and tab separated files are detected automatically. Excel files (`.xlsx`) need to be exported as CSV first. Max 5 MB and 10,000 rows.
- The file host's `robots.txt` is respected. A sheet that isn't public gives a clear error instead of an empty run.

#### Import from another Actor's dataset (e.g. Google Maps Scraper)

```json
{
  "domains": [],
  "datasetId": "<dataset ID>",
  "datasetField": "website"
}
```

- Pick the dataset in the input form (the Actor gets read-only access to it) or paste its ID. `datasetField` is optional: without it, the first of `domain`, `website`, `url`, `homepage` that has values is used. Google Maps results use `website`.
- To chain runs automatically, add this Actor as an integration of the scraper's task and set `"datasetId": "{{resource.defaultDatasetId}}"`.
- Up to 10,000 items are read. Items without a website are skipped. Links to social profiles (`facebook.com/yourshop`, `instagram.com/…`, `linkedin.com/company/…`) are reported as invalid instead of being analyzed, because the stack of Facebook says nothing about the business.

Domains from the list, the file and the dataset are combined (remove the example domains from the list if you only want the imported ones). Full URLs from files and datasets are reduced to the homepage, so `https://www.example.com/contact?utm_source=maps` and `example.com` are analyzed once. `inputUrl` keeps the original value, so you can join the results back to your sheet or dataset. Up to 10,000 unique domains per run; extra ones are skipped and reported in the `SUMMARY`.

#### Only sites using certain technologies

```json
{
  "domainsFileUrl": "https://example.com/leads.csv",
  "onlyWithTechnologies": ["Shopify", "WooCommerce", "Magento"]
}
```

Names are technology or category names exactly as they appear in the output (case does not matter), for example `HubSpot`, `Microsoft 365`, `Ecommerce`, `CMS` or `Live chat`. A site is output when it uses any of them. Misspelled names are reported in the log with suggestions; if none of the names is known, the run stops before analyzing anything.

**Billing with the filter: you pay per analyzed domain; the filter only trims the output.** Every domain that was analyzed is charged, including the ones the filter leaves out. Failed and blocked domains stay free. The `SUMMARY` shows how many matched and how many were filtered out.

#### Track technology changes

```json
{
  "domainsFileUrl": "https://example.com/prospects.csv",
  "monitorChanges": true,
  "stateKey": "prospects",
  "outputOnlyChanges": true
}
```

Schedule this weekly. Each record gets `changeStatus` (`new`, `changed` or `unchanged`), `technologiesAdded` and `technologiesRemoved` compared with the previous run that used the same `stateKey`. The first run marks every domain as `new`. With `outputOnlyChanges`, only new and changed domains are written to the dataset.

**Billing with monitoring: every analyzed domain is charged, also when it is unchanged and `outputOnlyChanges` hides it.** Failed and blocked domains stay free and keep their previous state.

### Output

One record per domain. This is a real record from a run on 2026-10-06 (the technologies list is shortened here; the run returned 15):

```json
{
  "domain": "allbirds.com",
  "inputUrl": "www.allbirds.com",
  "finalUrl": "https://www.allbirds.com/",
  "statusCode": 200,
  "title": "Allbirds: Comfortable, Sustainable Shoes & Apparel",
  "technologies": [
    { "name": "Shopify", "categories": ["Ecommerce"], "version": null, "confidence": 100 },
    { "name": "Cloudflare", "categories": ["CDN"], "version": null, "confidence": 100 },
    { "name": "Google Tag Manager", "categories": ["Tag managers"], "version": null, "confidence": 100 },
    { "name": "PayPal", "categories": ["Payment processors"], "version": null, "confidence": 100 },
    { "name": "Apple Pay", "categories": ["Payment processors"], "version": null, "confidence": 100 },
    { "name": "Attentive", "categories": ["Personalisation"], "version": null, "confidence": 100 },
    { "name": "Microsoft 365", "categories": ["Webmail", "Email"], "version": null, "confidence": 100 },
    { "name": "DocuSign", "categories": ["Miscellaneous"], "version": null, "confidence": 100 }
  ],
  "technologyCount": 15,
  "cms": null,
  "ecommercePlatform": "Shopify",
  "analytics": [],
  "tagManagers": ["Google Tag Manager"],
  "jsFrameworks": [],
  "cdn": "Cloudflare",
  "hosting": null,
  "webServer": null,
  "paymentProcessors": ["Apple Pay", "PayPal"],
  "marketingAutomation": [],
  "liveChat": [],
  "emailProvider": "Microsoft 365",
  "emailSecurityGateway": null,
  "dnsProvider": null,
  "mxHosts": ["allbirds-com.mail.protection.outlook.com"],
  "nameservers": ["ns2.markmonitor.com", "ns3.markmonitor.com", "ns4.markmonitor.com", "ns5.markmonitor.com", "ns6.markmonitor.com", "ns7.markmonitor.com"],
  "blocked": false,
  "blockedBy": null,
  "error": null,
  "checkedAt": "2026-10-06T15:24:23.171Z"
}
```

With **Include evidence**, each technology also lists what matched (real output from the same run):

```json
{
  "name": "Shopify",
  "categories": ["Ecommerce"],
  "version": null,
  "confidence": 100,
  "evidence": [
    "headers: powered-by: Shopify",
    "meta: shopify-digital-wallet=/11044168/digital_wallets/dialog",
    "scriptSrc: https://cdn.shopify.com/s/files/1/1104/4168/files/jsEncrypt.js?v=1756910700",
    "scriptSrc: https://www.allbirds.com/cdn/shop/t/4159/assets/theme2.js",
    "scripts: Shopify.shop = \"",
    "dns:CNAME: shops.myshopify.com"
  ]
}
```

A site behind a bot-protection challenge comes back marked, with no technologies, and is **not charged** (real record):

```json
{
  "domain": "canva.com",
  "inputUrl": "www.canva.com",
  "finalUrl": "https://www.canva.com/",
  "statusCode": 403,
  "title": "Canva",
  "technologies": [],
  "technologyCount": 0,
  "blocked": true,
  "blockedBy": "Cloudflare",
  "error": "blocked by bot protection (Cloudflare: cf-mitigated: challenge)",
  "checkedAt": "2026-10-06T15:24:34.329Z"
}
```

| Field | Meaning |
| --- | --- |
| `domain` / `inputUrl` / `finalUrl` | Host without `www.` / what you entered / page reached after redirects |
| `statusCode`, `title` | HTTP status and `<title>` of the analyzed page |
| `technologies` | Every detection: `name`, `categories`, `version` (when the site exposes it), `confidence` (0–100), `evidence` (optional) |
| `cms`, `ecommercePlatform` | Main CMS / shop platform seen on the website |
| `analytics`, `tagManagers`, `jsFrameworks` | Lists of analytics tools, tag managers, JavaScript frameworks |
| `cdn`, `hosting`, `webServer` | Infrastructure, from response headers and the site's DNS CNAME (not from scripts loaded off a CDN) |
| `paymentProcessors`, `marketingAutomation`, `liveChat` | Lists of payment, marketing-automation and chat tools |
| `emailProvider` | Mailbox provider from MX records. If MX points to a security gateway, it is read from SPF when possible |
| `emailSecurityGateway` | Proofpoint, Mimecast, Cisco, Barracuda… if MX points to one |
| `dnsProvider`, `mxHosts`, `nameservers` | DNS hosting and the raw MX / NS hostnames |
| `technologyCount` | Number of technologies |
| `blocked`, `blockedBy` | `true` + vendor (Cloudflare, DataDome, HUMAN, Imperva, Akamai, AWS WAF, Vercel…) when a challenge page was served |
| `error` | Why a domain could not be analyzed (DNS, timeout, HTTP 4xx/5xx, blocked, invalid input), or `null` |
| `changeStatus`, `technologiesAdded`, `technologiesRemoved` | Only with **Monitor technology changes**: `new` / `changed` / `unchanged` and what changed since the last run (`null` when the site could not be analyzed this time) |

DNS-based fields are `null` / empty when **Check DNS** is off. Technologies found only in DNS (SPF includes, domain-verification records such as `stripe-verification=` or `atlassian-domain-verification=`) mean the company has an account with that vendor. They don't mean the tool runs on the homepage, and they never fill the website fields (`cdn`, `paymentProcessors`…).

### Pricing

Pay per result: **$1.50 per 1,000 analyzed domains** ($0.0015 each), plus **$0.002 per run**. Platform usage is included. A domain is charged only when its website answered successfully (2xx/3xx) and was analyzed. Domains that don't resolve, time out, return an HTTP error, are invalid, or show a bot-protection challenge are returned with `error` / `blocked` and are **not charged**. So 1,000 domains where 800 answer cost about $1.20. Cap any run with Apify's *maximum cost per run*, and the Actor stops cleanly.

The price does not depend on the options. **You pay per analyzed domain; the technology filter and "output only changes" only trim the output**, so analyzed domains they hide are charged too. Importing from a file or a dataset costs nothing extra.

If a list turns out to be almost entirely dead (more than 80% of at least 200 checked domains fail), the run stops early with a message instead of working through the rest. Nothing is charged for the failed domains or for the ones not checked.

### Accuracy

We checked the Actor against an independent script on a fixed list of 50 well-known sites. The script uses plain string and header checks such as `/wp-content/`, `__NEXT_DATA__`, the `cf-ray` header, `x-vercel-id` and MX records. On the 44 sites both could load, across 21 technologies including WordPress, Shopify, Next.js, Cloudflare, Fastly, Vercel, Google Tag Manager and OneTrust:

- **Precision 97%**: 106 detections confirmed, 3 not confirmed.
- **Recall 97%**: 106 known technologies found, 3 missed.
- **Email provider**: right on **27 of 27** Google Workspace / Microsoft 365 domains.

We also used this list while tuning the detector, so expect somewhat lower accuracy on other sites — especially ones that load their tools through a tag manager (see Limitations).

Of the six disagreements:

- Two were content served differently to our servers than to the checking script.
- Two were tools the page only preconnects to.
- One was a WordPress site used headless.
- One was a Google Tag Manager comment the script doesn't count.

### Limitations

- **No JavaScript rendering.** We read the HTML, headers and DNS of one response. Tools that are injected later by another script, most often tags loaded *through* Google Tag Manager, and apps that build their page entirely in the browser can be missed. Script URLs written into the page's inline snippets *are* detected.
- **One page per input**: the homepage, or the URL you give. Technologies used only on other pages (checkout, blog) aren't seen.
- **Bot-protected sites** (Cloudflare challenge, DataDome, HUMAN, Imperva…) are reported as `blocked`, free of charge, instead of being bypassed. Requests come from Apify's cloud servers, which some sites challenge more often than home connections.
- **Versions** are reported only when the site exposes them (meta generator, file names, attributes).
- **Content can differ by country**: sites may serve different tags to visitors in different regions, and the Actor sees the version served to Apify's servers.
- **Fingerprints are a snapshot**: very new or niche tools may be missing. We update the bundled dataset periodically.
- Only public websites on standard ports (80/443) are analyzed. IP addresses and internal or private hostnames are refused.
- **Change monitoring** compares technologies with confidence 50 or more. Technologies known only from DNS (email provider, TXT verifications) are compared only when both runs had **Check DNS** on and every DNS query answered. Sites can also rotate scripts or A/B tests, which then shows up as a change. Runs of the same monitoring name should not overlap. A domain not seen for 180 days is forgotten.
- Each domain gets 25 seconds in total, across all fallback attempts, to answer. Very slow sites are reported as `timed out` (free).

### FAQ

**How is this different from browser extensions?** It runs on thousands of domains at once, returns structured data you can filter and export, and adds DNS insight (email provider, DNS-verified SaaS tools) that page-only tools don't show.

**Do you store cookies or personal data?** No. Only cookie *names* (e.g. `_shopify_y`) are used for detection, and their values are never kept. The Actor reads public website and DNS data only.

**What counts as "blocked"?** A recognized challenge or block page from a bot-protection vendor. A plain error such as HTTP 404 or 500 is reported in `error` with `blocked: false`. Neither is charged.

**How long do 10,000 domains take?** In our test, 500 domains took 2.3 minutes at default settings, which is about 45 minutes per 10,000. Lists with many dead domains take longer, because a site that doesn't answer can take up to 25 seconds; domains that don't exist in DNS usually fail within a second or two. A higher *Max concurrency* makes it faster (values below 10 are raised to 10). The default memory (1 GB) is enough and is also the maximum: our 500-domain test peaked at about 400 MB.

**Does the technology filter make the run cheaper?** No. Every analyzed domain is charged whether or not it matches; the filter saves you sorting the results. Failed and blocked domains are free either way.

**Why did my run stop early?** More than 80% of the first 200+ domains could not be fetched (domain doesn't exist, no answer, HTTP error). That usually means the wrong column was imported, or the list is old. The message and the `SUMMARY` (`stoppedEarly`) say how many failed. Check the input and run again; failed domains were not charged.

**Can I use the results of Google Maps Scraper or another Actor?** Yes: pick its dataset in **Dataset with websites**, or connect the two with an integration and pass `{{resource.defaultDatasetId}}`. Social-media profile links are skipped as invalid; real websites are reduced to their homepage and deduplicated.

**Where is the monitoring memory stored?** In a key-value store named `tech-stack-detector-state` in your own Apify account (one set of records per monitoring name). It holds only domain names, technology names and the date last seen. Delete it to start over.

**Where do the fingerprints come from?** From [webappanalyzer](https://github.com/enthec/webappanalyzer), the open community continuation of the Wappalyzer fingerprints (GPL-3.0), pinned to a fixed version, plus Folt Labs additions for HTML-visible framework markers and email, DNS and hosting providers.

### Credits & license

Technology fingerprints © the webappanalyzer contributors ([enthec/webappanalyzer](https://github.com/enthec/webappanalyzer), commit `eea872a`), licensed under the [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.html). The dataset files are bundled unmodified with their license. Our additions, plus one pattern we switched off because it was misleading, live in a separate file. Category names come from the same dataset.

# Changelog

This Actor's version history is a separate document: https://apify.com/folt/tech-stack-detector/changelog.md

# Actor input Schema

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

Websites to analyze, one per line: a domain (example.com — its homepage is analyzed) or a full URL (https://example.com/shop — that page is analyzed). Up to 10,000 per run; duplicates are removed. IP addresses and internal hostnames are not accepted. You can also (or instead) import websites from a file or a dataset below.

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

Looks up the domain's MX, NS, TXT and SOA records (and the CNAME of the website host) to detect the email provider (Google Workspace, Microsoft 365…), email security gateway, DNS provider, hosting, and SaaS tools that verified the domain (e.g. Atlassian, Stripe, HubSpot).

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

Adds to every technology the signals that matched (header, script URL, meta tag, DNS record…). Useful to audit results; makes the output larger.

## `domainsFileUrl` (type: `string`):

Public link to a CSV or TXT file, or a Google Sheets link (share the sheet as "Anyone with the link can view"; the tab in the link is used). The column named domain, website, url or homepage is used (or a header containing one of these words, e.g. "Company Website"); otherwise the first column. Comma, semicolon and tab separated files work. Max 5 MB and 10,000 rows. Excel files (.xlsx) must be exported as CSV first. The file host's robots.txt is respected.

## `datasetId` (type: `string`):

An Apify dataset whose items contain websites, for example the output of a Google Maps, LinkedIn company or lead-list scraper run. The Actor only reads it. The first 10,000 items are used; items without a website are skipped, and social-media profile links (Facebook, Instagram, LinkedIn…) are reported as invalid instead of being analyzed.

## `datasetField` (type: `string`):

Name of the item field that holds the website. Leave empty to detect it: the first of domain, website, url, homepage (case-insensitive) that has a value. A list value uses its first text entry.

## `onlyWithTechnologies` (type: `array`):

Technology or category names exactly as they appear in the output (case does not matter), e.g. Shopify, WordPress, HubSpot, Microsoft 365, or a category such as Ecommerce, CMS, Live chat. A domain is output when it uses any of them. You pay per analyzed domain; the filter only trims the output, so filtered-out domains are still charged. Unknown names are reported in the log; if none of the names is known, the run stops before analyzing anything.

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

Remembers each domain's technologies between runs and adds changeStatus (new / changed / unchanged), technologiesAdded and technologiesRemoved to every record. The first run marks every domain as new. Technologies with confidence below 50 are ignored, and DNS-only technologies are compared only when both runs had Check DNS on.

## `stateKey` (type: `string`):

Runs with the same name compare against each other; use a different name for each list you monitor (letters, digits and ! - \_ . ' ( ), up to 100 characters). Empty means "default". The memory is kept in your account's key-value store "tech-stack-detector-state"; a domain not seen for 180 days is forgotten.

## `outputOnlyChanges` (type: `boolean`):

With Monitor technology changes on, writes only domains whose stack is new or changed to the dataset. Unchanged, failed and blocked domains are counted in the run SUMMARY only. You pay per analyzed domain: unchanged domains are still charged even though they are not output.

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

How many websites to analyze in parallel. Higher values finish large lists faster; the default memory (1 GB) is enough. Values below 10 are raised to 10.

## Actor input object example

```json
{
  "domains": [
    "wordpress.org",
    "nextjs.org",
    "www.allbirds.com"
  ],
  "checkDns": true,
  "includeEvidence": false,
  "monitorChanges": false,
  "outputOnlyChanges": false,
  "maxConcurrency": 20
}
```

# Actor output Schema

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

One record per domain with detected technologies, email provider and infrastructure.

## `allFields` (type: `string`):

The same records with every field, as JSON.

## `summary` (type: `string`):

Per-input status, counts and anything that could not be processed.

# 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",
        "nextjs.org",
        "www.allbirds.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("folt/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 = { "domains": [
        "wordpress.org",
        "nextjs.org",
        "www.allbirds.com",
    ] }

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

```

## MCP server setup

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