# Website Tech Stack Detector (`martellosystems/website-tech-stack-detector`) Actor

Give it a list of domains. For each, it fetches the homepage and deterministically detects the technology stack from response headers, HTML, scripts, and JSON-LD. No LLM. One record per domain, technologies grouped by category.

- **URL**: https://apify.com/martellosystems/website-tech-stack-detector.md
- **Developed by:** [Martello Systems](https://apify.com/martellosystems) (community)
- **Categories:** Developer tools, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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/platform/actors/running/actors-in-store#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

## Website Tech Stack Detector

**Every detection comes with the exact signal that produced it.** Feed it a list of
domains, get back one record per domain naming the CMS, framework, analytics,
CDN, ecommerce, marketing and payment tools each site runs, plus the header,
script URL, cookie or markup fingerprint that proved it.

No LLM. No API key. No confidence scores to second-guess.

```json
{ "domains": ["shopify.com", "wordpress.org", "vercel.com"] }
```

### Why this one

Most tech-stack detectors hand you a list of names and ask you to trust it. When
a name is wrong you have no way to tell, and when it is right you cannot show
anyone why. This actor records the evidence for every single hit:

```json
{"tech": "Shopify", "category": "ecommerce", "source": "script_src", "evidence": "cdn.shopify.com"}
```

That matters when you are acting on the output rather than browsing it: routing a
lead because a prospect runs Shopify, sizing a migration, or filing a competitive
brief someone else will check.

| | This actor |
|---|---|
| **Detection method** | Deterministic signature matching. Same site, same input, same answer, every run. |
| **Evidence** | Every hit carries `source` and `evidence`, the literal string that matched. |
| **Failed domains** | Still get a row, with the error. A 500-domain run never silently returns 480. |
| **Billing** | You are charged only for domains that were actually fingerprinted. Dead domains are free. |
| **Charge limits** | Set `maxTotalChargeUsd` and the run trims the input up front. It stops the crawling, not just the invoice. |
| **Coverage** | 49 technologies across 7 categories, listed in full below. No inflated count. |
| **Cost** | $0.01 per domain analyzed. |

**On coverage, plainly:** 49 signatures is a focused set, not an exhaustive one.
It covers the tools that actually change a commercial decision. If you need the
long tail of 2,000 obscure libraries, a Wappalyzer-database actor is the better
buy. If you need to know whether 4,000 prospects are on Shopify or WooCommerce
and be able to prove it, this is built for that.

### What it detects

| Category | Technologies |
|---|---|
| **CMS** | Drupal, Ghost, HubSpot CMS, Joomla, Squarespace, Webflow, Wix, WordPress |
| **Framework** | Angular, ASP.NET, Express, Gatsby, Laravel, Next.js, Nuxt.js, React, Ruby on Rails, Svelte / SvelteKit, Vue.js |
| **Analytics** | Amplitude, Facebook Pixel, Google Analytics, Google Tag Manager, Hotjar, Mixpanel, Plausible, Segment |
| **CDN / hosting** | Akamai, Amazon CloudFront, Apache, Cloudflare, Fastly, Netlify, Nginx, Vercel |
| **Ecommerce** | BigCommerce, Magento, Shopify, WooCommerce |
| **Marketing** | Drift, HubSpot, Intercom, Klaviyo, Mailchimp, Zendesk |
| **Payments** | Braintree, PayPal, Square, Stripe |

Signals read per domain: response headers, `Set-Cookie`, the `<meta generator>`
tag, every `<script src>` and `<link href>`, code fingerprints in the HTML body,
and JSON-LD blocks.

### Use it for

- **Lead routing and scoring.** Segment a prospect list by ecommerce platform,
  payment processor or marketing stack before anyone writes an email.
- **Competitive intelligence.** Snapshot a set of competitors' stacks and diff it
  on a schedule to catch a re-platform.
- **Migration and agency scoping.** Know what a site runs before quoting the work.
- **Partner and integration targeting.** Find every company already running the
  tool you integrate with.

### Input

| Field | Required | Description |
|---|---|---|
| `domains` | yes | List of domains or homepage URLs. One record per entry. `example.com`, `www.example.com` and `https://example.com` all work; a bare domain gets `https://` prefixed. |

Homepage only, one GET per domain, so a large list stays fast and cheap.

### Output

One dataset record per domain, always emitted:

```json
{
  "domain": "shopify.com",
  "url": "https://shopify.com",
  "final_url": "https://www.shopify.com/",
  "status_code": 200,
  "ok": true,
  "technologies": {
    "ecommerce": ["Shopify"],
    "cdn": ["Cloudflare", "Fastly"],
    "analytics": ["Google Analytics", "Segment"],
    "framework": ["React"],
    "marketing": ["HubSpot"],
    "payments": ["Stripe"]
  },
  "all_technologies": ["Cloudflare", "Fastly", "Google Analytics", "HubSpot", "React", "Segment", "Shopify", "Stripe"],
  "raw_signals": {
    "server_header": "cloudflare",
    "x_powered_by": null,
    "meta_generator": null,
    "matched": [
      {"tech": "Shopify", "category": "ecommerce", "source": "script_src", "evidence": "cdn.shopify.com"}
    ]
  }
}
```

A domain that could not be fetched still ships a record, with `ok: false` and an
`error` string, so a bulk run is auditable end to end.

The run also writes a `SUMMARY` record to the key-value store with
`domains_analyzed`, `domains_ok` and `tech_frequency`, which is the fastest way
to answer "what is the most common stack across this list".

### Pricing and cost control

**$0.01 per domain analyzed**, charged on the `website-analyzed` event, plus
Apify's $0.00005 actor start. Platform usage is included.

A domain that fails to fetch is **not** charged.

Set `maxTotalChargeUsd` on the run and the actor trims the domain list to what
that budget covers *before* it starts fetching, then re-checks inside the loop.
The cap stops the work, not just the billing, so an over-budget run does not
quietly burn proxy bandwidth you are not paying for. The log line
`[charge-guard] this run can bill N ... trimming to N` records exactly what was
dropped.

### Notes

- All egress is routed through the Apify residential proxy, so bulk runs are not
  blocked on a single IP.
- Detection is homepage-only by design. It is what makes a 5,000-domain run cheap
  and predictable. Sites that render everything client-side after the first paint
  will report fewer technologies.
- Nothing is inferred or guessed. If a technology is not in the table above, this
  actor will not report it, and it will never invent one to fill a row.

Built by [Martello Systems](https://apify.com/martellosystems).

# Actor input Schema

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

One domain or homepage URL per line. Each entry produces exactly one output record, and you are charged only for the ones that fetch successfully. Bare domains work (example.com), as do full URLs (https://example.com) and www hosts. Only the homepage is fetched, one request per entry, so large lists stay fast and cheap.

## Actor input object example

```json
{
  "domains": [
    "shopify.com",
    "wordpress.com"
  ]
}
```

# Actor output Schema

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

One record per domain: detected technologies plus the evidence for each.

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

SUMMARY / OUTPUT record in the key-value store: counts and per-item status for the run.

# 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": [
        "shopify.com",
        "wordpress.com"
    ]
};

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

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

```

## MCP server setup

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