# Tech Stack Scanner | Shopify Apps & WordPress Plugins (`adored_maze/tech-stack-scanner`) Actor

Detect 130+ web technologies, Shopify apps, WordPress plugins with versions, the ads.txt ad stack, and an A-F security header grade. Re-run on a schedule for install, migration and posture-drift alerts.

- **URL**: https://apify.com/adored\_maze/tech-stack-scanner.md
- **Developed by:** [Bizking Jang](https://apify.com/adored_maze) (community)
- **Categories:** E-commerce, Developer tools, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event + usage

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Tech Stack Scanner & Shopify App Detector

Detect what any website runs — ecommerce platform, CMS, framework, analytics, ad pixels, payments, support chat, CDN, hosting — plus, on Shopify stores, **which apps are installed**, the theme, and Plus status. Re-run the same list and it tells you **exactly what changed**.

**No API key. No proxy. No login.** One public request per domain.

### Shopify app detector

Point it at a store and get the installed apps: Klaviyo, Attentive, Postscript, Gorgias, Yotpo, Okendo, Judge.me, Loox, Recharge, Rebuy, Smile.io, Triple Whale, Northbeam, Elevar, Swym, Back in Stock, PageFly, Shogun and more — with the theme name, whether it's a custom theme, currency and country.

### Find stores using Klaviyo, Yotpo, Recharge or any other app

Pass a list of stores and `mustUseAny: ["Yotpo"]`. You get back only the stores running it. That is a list of merchants who have already proven they pay for that category of app — they just picked someone else.

### Shopify app install and uninstall alerts

Re-run weekly and the Actor reports app changes per store: `added: ["Klaviyo"]`, `removed: ["Yotpo"]`. **A store that just removed a review app is shopping for a review app this week.** That is a two-week window, and it is the whole reason to run this on a schedule instead of once.

### WordPress plugin detector

Every plugin a WordPress site loads — not a curated list, the actual slugs from `/wp-content/plugins/`, so custom and private plugins show up too. With version numbers where the site exposes them, the active theme, WordPress core version, and a flag for slugs that look commercial (`-pro`, `-premium`).

### Find WordPress sites running an outdated plugin version

Plugin slug plus version is exposure data. Scan a list, filter on `plugins_with_version`, and you have the sites running a given plugin below a given release — which is what security vendors and researchers actually want and what plugin vendors use to prompt upgrades.

### Ad monetisation stack from ads.txt

Parse the publisher's own IAB `ads.txt`: every ad system authorised to sell its inventory, which are DIRECT and which are RESELLER, the managed partner running monetisation (Mediavine, Raptive, Ezoic, AdPushup, Freestar…), and `OWNERDOMAIN`, which frequently reveals the parent media group. When a publisher switches ad partners, this file changes first.

### Security header posture and grade

Every scan returns an A–F grade computed from CSP quality (nonce/hash/strict-dynamic vs `unsafe-inline`/`unsafe-eval`/wildcards), HSTS max-age and preload, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP/CORP, cookie flags, and version disclosure in `Server` or `X-Powered-By`. No extra request — it is read off the response already fetched.

### Website technology lookup

130+ technologies across 24 categories, each with a confidence level and a version where the site exposes one.

### Tech stack change monitoring

Every re-run diffs against your previous scan and returns `added`, `removed` and `previous_scan_at`. WooCommerce → Shopify. Zendesk → Gorgias. Universal Analytics → GA4. Those migrations are buying events.

***

### Why this beats a plain tech-lookup scraper

| | This Actor | Typical tech-detection scraper |
|---|---|---|
| Change detection between runs | **Yes — added/removed + previous scan date** | No |
| Shopify apps installed | **35 apps, categorised** | No |
| WordPress plugins | **All of them, by slug, with versions** | Rarely, and only a fixed list |
| Shopify theme, custom-theme flag, Plus status | **Yes** | No |
| App install/uninstall events | **Yes** | No |
| Confidence levels | **high / medium / low, each explained** | Flat yes/no |
| Evidence model | **Headers + loaded asset URLs weighted above body text** | Any string match anywhere |
| Version detection | **Yes** (WordPress, Angular, jQuery, nginx, WooCommerce…) | Rare |
| Filter to only changed domains | **Yes** — built for scheduled monitoring | No |
| ads.txt monetisation stack | **Parsed and normalised** | No |
| Security header grade | **A–F with the reasoning** | No |
| Proxy required | **No** | Often |

**On confidence, because it is the difference between a usable list and a noisy one:** most detectors match a technology's name anywhere on the page, so a blog post mentioning Shopify marks the site as running Shopify. This Actor separates *load-bearing* evidence — response headers and the URLs the page actually pulls scripts from — from body text. Something referenced only in prose is reported at `low`, and a single stray mention is not reported at all.

***

### Output

```json
{
  "domain": "graza.co",
  "status": 200,
  "ok": true,
  "server": "cloudflare",
  "technology_count": 9,
  "technology_names": ["Shopify", "Shop Pay", "Cloudflare", "Google Tag Manager"],
  "categories": ["cdn", "ecommerce", "payments", "tag-manager"],
  "technologies": [
    { "name": "Shopify", "category": "ecommerce", "version": null, "confidence": "high", "signals": 2 }
  ],
  "shopify": {
    "is_shopify": true,
    "is_shopify_plus": false,
    "theme_name": "baggy – production",
    "theme_store_id": null,
    "is_custom_theme": true,
    "currency": "USD",
    "country": "US",
    "app_count": 5,
    "app_names": ["Klaviyo", "Postscript", "Okendo", "Elevar", "Triple Whale"],
    "app_categories": ["attribution", "email-sms", "reviews"],
    "apps": [
      { "name": "Klaviyo", "category": "email-sms", "confidence": "high", "evidence": "asset" },
      { "name": "Elevar", "category": "attribution", "confidence": "medium", "evidence": "inline" }
    ]
  },
  "wordpress": null,
  "ads_txt": null,
  "security": {
    "grade": "B",
    "score": 81,
    "https": true,
    "headers_missing": ["Permissions-Policy", "Cross-Origin-Resource-Policy"],
    "hsts": { "max_age_days": 730, "include_subdomains": true, "preload": true },
    "csp": { "present": true, "weaknesses": ["unsafe-inline"], "strengths": ["hash", "frame-ancestors"] },
    "information_disclosure": [],
    "cookie_issues": ["1 cookie(s) without HttpOnly"]
  },
  "changes": {
    "added": ["shopify-app:Klaviyo"],
    "removed": ["shopify-app:Yotpo"],
    "previous_scan_at": "2026-08-02T09:14:00+00:00"
  },
  "first_scan": false,
  "scanned_at": "2026-08-09T15:42:00+00:00"
}
```

On a WordPress site the `wordpress` block appears instead:

```json
{
  "wordpress": {
    "is_wordpress": true,
    "core_version": "7.0.3",
    "rest_api_exposed": true,
    "themes": [{ "slug": "hello-elementor", "version": "3.4.6" }],
    "plugin_count": 5,
    "plugin_slugs": ["elementor", "elementor-pro", "pojo-accessibility", "weglot"],
    "known_plugin_names": ["Elementor", "Elementor Pro", "Weglot"],
    "commercial_plugin_slugs": ["elementor-pro"],
    "plugins_with_version": { "elementor": "4.2.0", "elementor-pro": "4.2.0" },
    "latest_upload_month": "2026-05"
  }
}
```

`changes` is `null` on a domain's first scan and whenever `trackChanges` is off. Changes are prefixed so you can tell them apart: `shopify-app:Klaviyo`, `wp-plugin:wordfence`, `ad-system:mediavine.com`, `server:cloudflare`, `sec-header:Content-Security-Policy`, or a bare name for a platform-level technology.

That means a hosting migration, an ad-partner switch, or a dropped security header all arrive as change events on the same feed.

### Input

```json
{
  "domains": ["graza.co", "allbirds.com", "https://www.deathwishcoffee.com"],
  "shopifyOnly": true,
  "wordpressOnly": false,
  "mustUseAny": ["Yotpo"],
  "trackChanges": true,
  "onlyChanged": false,
  "categories": ["ecommerce", "reviews"],
  "includeAdsTxt": true,
  "securityGradeAtMost": "C"
}
```

Only `domains` is required. `shopifyOnly` keeps Shopify stores; `mustUseAny` keeps stores running a named technology or app; `onlyChanged` turns the run into a pure change feed.

### Pricing

| Event | Price |
|---|---|
| `domain` — one domain scanned | **$0.05** |
| `change` — a technology, app, plugin, ad system, host or security header added/removed vs your last scan | **$0.05** |

You are charged for a change **only when something actually changed**. A quiet week over 1,000 monitored stores costs you the scans and nothing more.

### Use cases

**WordPress plugin developers** — find the sites running your competitor, and see which are on an old version of yours.

**Security vendors and researchers** — plugin slug plus version across a monitored universe is exposure data, refreshed weekly.

**Shopify app developers** — build a list of every store running your competitor, and get alerted the week a store uninstalls them. That is the highest-intent list in your market.

**SaaS competitive teams** — who runs Mailchimp instead of Klaviyo, Zendesk instead of Gorgias, and who just switched.

**Agencies** — audit a prospect's stack before the pitch; audit a client's for redundant or abandoned tooling.

**Cyber-insurance underwriters and third-party-risk teams** — a header grade per vendor, refreshed weekly. Posture drifts silently: a header gets dropped in a deploy and nobody notices until the diff shows it.

**Adtech sales** — who a publisher's current ad partner is, and the week they switch.

**Investors and analysts** — measure real adoption of a SaaS product across a defined universe of sites, month over month.

**Anyone currently paying for a technology-lookup subscription** who only needs the lookup and the diff.

### Scheduling

Point an Apify schedule at your domain list, set `onlyChanged: true`, and you get a feed containing only the sites whose stack moved. History is kept per domain in a named key-value store, so it survives across runs and schedules.

### Call it as an HTTP API

The Actor runs in Standby, so you can query it directly instead of launching a run:

```
GET https://adored-maze--tech-stack-scanner.apify.actor/?domains=graza.co,wpbeginner.com
Authorization: Bearer <your Apify token>
```

```json
[
  { "domain": "graza.co", "security": { "grade": "D" },
    "shopify": { "app_names": ["Klaviyo", "Postscript", "Okendo"] } },
  { "domain": "wpbeginner.com", "security": { "grade": "E" },
    "wordpress": { "known_plugin_names": ["OptinMonster", "WPForms", "Perfmatters"] } }
]
```

Query parameters: `domains` (comma-separated, up to 50 per request), `adsTxt=true`, `trackChanges=true`. Response is the same record shape as the dataset. Call it from a script, a workflow, or an agent — one domain at a time, in a loop, as often as you like.

### Use with AI agents

Small, deterministic, one job. Callable over the Apify API and through MCP, and enabled for agentic payments — a good tool for an agent answering "what is this company running?" mid-conversation.

### Technologies detected

`ecommerce` Shopify · Shopify Plus · WooCommerce · Magento · BigCommerce · Salesforce Commerce Cloud · PrestaShop · Wix Stores · Cafe24
`cms` WordPress · Drupal · Ghost · Contentful · Sanity · Webflow · Squarespace · Wix · Framer · HubSpot CMS
`framework` Next.js · Nuxt · Remix · Gatsby · SvelteKit · Astro · Rails · Django · Laravel · ASP.NET
`analytics` GA4 · Adobe · Amplitude · Mixpanel · PostHog · Heap · Hotjar · Clarity · FullStory · Plausible · Naver Analytics
`advertising` Meta Pixel · TikTok · LinkedIn · Pinterest · Reddit · Criteo · Taboola · Outbrain · Kakao Pixel
`marketing` HubSpot · Marketo · Pardot · Klaviyo · Braze · ActiveCampaign · Customer.io
`payments` Stripe · PayPal · Adyen · Braintree · Klarna · Afterpay · Shop Pay · Toss Payments · PortOne
`support` Intercom · Zendesk · Drift · Crisp · Freshworks · Channel Talk
`infra` Cloudflare · Fastly · Akamai · CloudFront · Vercel · Netlify · nginx · Apache
…plus A/B testing, error tracking, search, reviews, consent, video and maps.

### Troubleshooting

**A domain returns `ok: false`** — the site refused the connection, timed out, or does not resolve. Both `https` and `http` are attempted.

**A domain returns `blocked: true`** — the site answered with a bot wall instead of its homepage, and `block_reason` names which one (`cloudflare-challenge`, `datadome`, `http-403-forbidden`, `perimeterx`, …). This is reported as a block on purpose. A wall returns a small page with no technologies on it, and calling that a successful scan would put a confident wrong answer in your spreadsheet — and, worse, would make the next successful scan look like the site adopted its entire stack overnight. Blocked domains never touch change history.

**Fewer technologies than another tool reports** — deliberate. Tools that scan body text over-report. Everything at `high` confidence here is backed by a header or a loaded asset.

**A Shopify app you know is installed doesn't appear** — apps that render only inside checkout, the customer account area, or the admin are not visible from the storefront homepage. Apps injected purely at runtime by a tag manager may appear at `medium` with `evidence: "inline"` rather than `high`.

**Client-rendered sites show less on the fast path** — the first attempt reads the server response rather than a rendered page, which is what keeps a normal run cheap. Domains that come back walled are then re-opened in a real browser, and those rows carry `rescued_with_browser: true`.

### FAQ

**Is this legal?** It fetches one public homepage per domain, unauthenticated — the same request any browser makes. No login, no paywall. When a site is walled it is opened once in an ordinary browser, which is what a visitor does; nothing solves a CAPTCHA and a wall that holds is reported as a wall.

**Any personal data?** None. The subject is a website's software. No people are identified, contacted, or profiled.

**How many domains per run?** No hard cap. 20 are fetched concurrently; parsing runs in separate worker processes so a heavy page cannot stall the requests still in flight.

**What share of domains actually come back with data?** On a list of 82 live commercial sites, 77 scanned, 4 stayed behind a wall a browser could not pass, and 1 never answered. On the tail of a public top-domain list the walled share is higher, because the largest sites run the strictest bot protection. The number you should not accept from any scanner is a silent zero.

### Legal

Not affiliated with, endorsed by, or sponsored by Shopify or any technology vendor detected. All trademarks belong to their respective owners.

# Actor input Schema

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

Domains or URLs to scan, e.g. shopify.com or https://www.allbirds.com. Re-run the same list on a schedule to get change alerts.

## `trackChanges` (type: `boolean`):

Remember each domain's stack and report what was added or removed since the previous scan. This is what makes repeat runs worth it - a site switching from WooCommerce to Shopify is the signal you are buying.

## `onlyChanged` (type: `boolean`):

Skip domains whose stack is identical to last scan. Ideal for a daily or weekly monitoring job over a large domain list.

## `mustUseAny` (type: `array`):

Keep only domains running at least one of these technologies, e.g. Shopify, Klaviyo, HubSpot. Exact technology names as returned in the output.

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

Keep only domains with a detection in one of these categories: ecommerce, cms, framework, javascript, analytics, tag-manager, advertising, marketing-automation, crm, payments, support, ab-testing, error-tracking, cdn, hosting, security, search, reviews, consent, video, maps.

## `shopifyOnly` (type: `boolean`):

Keep only domains detected as Shopify stores. Combine with mustUseAny to find, for example, every Shopify store running Klaviyo.

## `wordpressOnly` (type: `boolean`):

Keep only domains detected as WordPress. Combine with mustUseAny to find, for example, every WordPress site running Elementor Pro.

## `includeAdsTxt` (type: `boolean`):

Fetch and parse /ads.txt to list every ad system the site authorises to sell its inventory, which are DIRECT vs RESELLER, and which managed ad partner runs its monetisation. One extra small static request per domain.

## `securityGradeAtMost` (type: `string`):

Keep only domains whose security-header grade is this or worse (A is best, F is worst). Set to C to surface the weakest sites in a monitored list. Leave empty for no filter.

## `retryBlockedViaProxy` (type: `boolean`):

When a site answers with a bot wall (Cloudflare challenge, HTTP 403/429, DataDome and similar) instead of its homepage, try once more from a different IP. Blocked domains are always reported as blocked rather than as a site with no technologies, so turning this off costs coverage, not accuracy.

## `proxyGroups` (type: `array`):

Apify Proxy groups to use for the blocked-domain retry, e.g. RESIDENTIAL. Leave empty for the automatic selection.

## `rescueBlockedWithBrowser` (type: `boolean`):

When a site answers with a bot wall instead of its homepage, open it in a real browser and scan what loads. Measured recovery is roughly two thirds of blocked domains, including Cloudflare-protected DTC stores and publishers. Only blocked domains reach the browser, so an unblocked list costs nothing extra. Rescued rows are marked with rescued\_with\_browser.

## Actor input object example

```json
{
  "domains": [
    "allbirds.com",
    "techcrunch.com",
    "notion.so"
  ],
  "trackChanges": true,
  "onlyChanged": false,
  "mustUseAny": [],
  "categories": [],
  "shopifyOnly": false,
  "wordpressOnly": false,
  "includeAdsTxt": true,
  "retryBlockedViaProxy": true,
  "proxyGroups": [],
  "rescueBlockedWithBrowser": true
}
```

# 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": [
        "allbirds.com",
        "techcrunch.com",
        "notion.so"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("adored_maze/tech-stack-scanner").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": [
        "allbirds.com",
        "techcrunch.com",
        "notion.so",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("adored_maze/tech-stack-scanner").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": [
    "allbirds.com",
    "techcrunch.com",
    "notion.so"
  ]
}' |
apify call adored_maze/tech-stack-scanner --silent --output-dataset

```

## MCP server setup

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

```

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/Aa4zNOGxyWbztSOPx/builds/Pe1wAyZKeTqYyyfzN/openapi.json
