# Tech Stack Detector – 5,800 Technologies with Versions (`locomint/tech-stack-detector`) Actor

Tech stack detector for a list of websites: one plain HTTP request per page, no browser, and 5,800 technologies identified from the response - CMS, ecommerce, analytics, frameworks, hosting, CDN, payment and ad tech - with the version where the page reveals one. Pay only for pages that answer.

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

## Pricing

from $8.00 / 1,000 result delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#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 Detector – 5,800 Technologies with Versions

This tech stack detector tells you what any website runs. Paste a list of domains or URLs and get
one row per site: the CMS, the ecommerce platform, the JavaScript framework, the analytics, the
tag manager, the payment processor, the CDN, the web server and the hosting behind it - with the
version number wherever the page reveals one.

It works from **one plain HTTP request per page**. No browser, no JavaScript engine, no login and
no proxy. That is why it is fast, why a run of a thousand sites takes minutes rather than hours,
and why it can be priced the way it is.

### What it does

Every line of input becomes one page to read:

- **A bare domain** such as `shopify.com` is read as `https://shopify.com/`.
- **A full URL** such as `https://www.gymshark.com/` is used as given.
- **Any deeper page** such as `https://example.com/pricing` works too, and sometimes tells you
  more than the homepage - checkout and pricing pages carry payment and A/B testing tags that the
  front page does not.

Duplicates are removed, including the `https://acme.com`, `https://acme.com/`, `https://ACME.com/`
and `https://acme.com/#top` spellings of the same page, so a column pasted out of a spreadsheet is
charged once per site. Anything that is not a domain or a URL is named in the run log and skipped.

The page is then fetched once over a direct connection and matched against a fingerprint set of
**7,610 technologies**, of which **5,834 can be identified from a single plain response** - 76.7%.
Seven kinds of evidence are read out of that one request:

| Evidence | Examples of what it finds |
|---|---|
| Response headers | Nginx, Apache, Cloudflare, Fastly, Vercel, Varnish, HSTS, Amazon S3 |
| Cookies | Shopify, WooCommerce, Laravel, ASP.NET, session and consent platforms |
| Meta tags | Generator tags: WordPress and its version, Drupal, Joomla, Astro, Hugo, Gatsby |
| The markup | Bootstrap, Tailwind, Font Awesome, Elementor, theme and plugin paths |
| Inline scripts | Google Tag Manager containers, Segment, Hotjar, Klaviyo, chat widgets |
| Script URLs | Every hosted library, pixel, CDN and SaaS widget the page loads |
| The URL itself | Platform-hosted domains such as `myshopify.com` or `squarespace.com` |

On top of the direct matches, a technology's stated implications are expanded - WordPress implies
PHP and MySQL - and its stated requirements are honoured, so a WooCommerce fingerprint on a page
with no WordPress is not reported. That is most of the difference between a plausible answer and
a credible one.

### What is in a row

| Field | What goes in it |
|---|---|
| `url` | The page that was fetched, normalised from what you typed. |
| `final_url` | Where it ended up after redirects. Different from `url` means the site moved you. |
| `status` | `ok` when technologies were found, `no_match` when the page was read and nothing matched. |
| `http_status` | The HTTP status of the page that was scanned. Always under 400 - see below. |
| `tech_count` | How many technologies are in this row. |
| `technologies` | One object per technology: `name`, `categories` (one or more of 107), `version` or `null`. |
| `tech_names` | The same findings as a flat list of proper names - `["WordPress", "Google Tag Manager"]` - which is the column you want in a CSV. |
| `tech_slugs` | The same findings as slugs - `["wordpress", "google-tag-manager"]` - for joining against a table you already have. |
| `categories` | The distinct categories present, most defining first - a CMS before a font library. |
| `scan_time_ms` | Milliseconds from starting the fetch to finishing detection, for this page. |
| `fetched_at` | ISO 8601 UTC, when the page was read. |
| `input` | The line you typed, so a row can be matched back to your sheet. |

#### The category filter is free

Name one or more categories and only those technologies are reported - and **a page with nothing
in those categories is left out of the dataset entirely and is not charged for**. So "every
Shopify site in this list" costs you only the Shopify sites.

A real run of eight sites with `categoriesFilter: ["CMS", "Ecommerce"]` delivered four rows and
charged for four: Rolling Stone (WordPress 6.9.7), Allbirds (Shopify), Gymshark (Contentful and
Shopify) and TechCrunch (WordPress 6.9.7). Basecamp, Figma, Mozilla and Vercel run neither, so
they produced no row at all.

Spelling is matched case-insensitively against the real category list, and anything unrecognised
is named in the run log rather than silently matching nothing.

#### A page that gives no answer costs nothing

Only a page that was actually read produces a row. Every one of these produces none, is counted
and named in the run log with its reason, and costs you nothing:

| What happened | Why there is no row |
|---|---|
| The site did not answer, or refused the connection | There is nothing to report. |
| The site answered with an anti-bot challenge | The host is backed off from and never retried from another address. |
| The site answered HTTP 400 or worse | The headers of a 404 page would still name a CDN, but that is an answer about an error page, not about the site. |
| The URL was not a web page | A PDF, an image or an archive has no stack of its own; all a fingerprint set could find is the CDN serving the file. Brochure links turn up in most pasted lists. |
| The page did not answer inside the time limit | Reported with the limit that was in force. |
| The address failed the safety check | Loopback, private, link-local and cloud metadata addresses are refused. |
| Every technology was removed by your category filter | You asked not to see it. |

### Who it is for

- **Sales and business development** qualifying accounts by what they already run - find the
  Shopify Plus stores, the HubSpot users, the sites still on a platform you replace.
- **Agencies and consultancies** auditing a prospect list before the pitch, or checking which of
  their own clients are still on an old CMS or an unpatched plugin version.
- **Competitive and market research** measuring platform share across an industry or a country:
  how many of these 2,000 retailers run Shopify, and how many moved this year.
- **Recruiters and technical sourcers** finding companies that use the stack a candidate knows.
- **Security and compliance teams** taking an inventory of a domain portfolio, including the
  version strings that say which sites are behind.
- **Data and AI teams** who want a typed technology dataset with a timestamp per row.

### How to use it

In the Apify Console:

1. Paste your sites into **Websites to scan**, one per line, up to 5,000 per run.
2. Leave **Include version numbers** on unless you only want names.
3. Optionally name the technology categories you care about. The filter is free.
4. Start the run and export the dataset as CSV, Excel or JSON.

From the API, this call starts a run, waits for it and returns the rows:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/locomint~tech-stack-detector/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["gymshark.com", "techcrunch.com", "https://stripe.com/"]}'
```

The synchronous endpoint waits up to 300 seconds. For a long list, start the run with
`POST https://api.apify.com/v2/acts/locomint~tech-stack-detector/runs?token=YOUR_APIFY_TOKEN` and
read its dataset when the run finishes. With the Python client (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("locomint/tech-stack-detector").call(
    run_input={
        "urls": ["gymshark.com", "allbirds.com", "basecamp.com", "techcrunch.com"],
        "categoriesFilter": ["CMS", "Ecommerce"],
        "versions": True,
    }
)
for row in client.dataset(run.default_dataset_id).iterate_items():
    print(row["url"], row["tech_count"], row["tech_names"])
```

### Input example

```json
{
  "urls": [
    "wordpress.org",
    "www.gymshark.com",
    "https://techcrunch.com/",
    "https://www.python.org/"
  ],
  "versions": true,
  "categoriesFilter": [],
  "concurrency": 5,
  "timeoutSeconds": 20
}
```

| Field | Default | Allowed | What it does |
|---|---|---|---|
| `urls` | required | 1 to 5,000 | Domains or URLs, one per line. Duplicates removed. |
| `versions` | `true` | true / false | Report the version where the page reveals one. Same price either way. |
| `categoriesFilter` | empty | any category names | Keep only these categories. Free: a page with none is not delivered and not charged. |
| `concurrency` | 5 | 1 to 10 | Pages fetched at the same time. |
| `timeoutSeconds` | 20 | 5 to 90 | A page with no answer by then is left out, and not charged. |

The 107 categories include CMS, Ecommerce, Analytics, JavaScript frameworks, Web frameworks, Web
servers, CDN, Hosting, PaaS, IaaS, Payment processors, Buy now pay later, Tag managers, Marketing
automation, CRM, Live chat, SEO, Advertising, Retargeting, Security, Cookie compliance, Email,
Reviews, Page builders, WordPress plugins, WordPress themes, Shopify apps, Shopify themes,
Databases, Programming languages, Static site generator, Reverse proxies, Caching, A/B Testing,
Form builders, Appointment scheduling, Shipping carriers and Artificial Intelligence.

### Output example

Two real rows from a run on 12 September 2026.

```json
{
  "url": "https://techcrunch.com/",
  "final_url": "https://techcrunch.com/",
  "status": "ok",
  "http_status": 200,
  "tech_count": 12,
  "technologies": [
    { "name": "WordPress", "categories": ["CMS", "Blogs"], "version": "6.9.7" },
    { "name": "WordPress Block Editor", "categories": ["Page builders"], "version": null },
    { "name": "MySQL", "categories": ["Databases"], "version": null },
    { "name": "PHP", "categories": ["Programming languages"], "version": null },
    { "name": "Nginx", "categories": ["Web servers", "Reverse proxies"], "version": null },
    { "name": "WordPress VIP", "categories": ["PaaS"], "version": null },
    { "name": "Yoast SEO", "categories": ["SEO", "WordPress plugins"], "version": "25.1" },
    { "name": "Yoast SEO Premium", "categories": ["SEO"], "version": "25.1" },
    { "name": "Google Tag Manager", "categories": ["Tag managers"], "version": null },
    { "name": "HSTS", "categories": ["Security"], "version": null },
    { "name": "reCAPTCHA", "categories": ["Security"], "version": null },
    { "name": "Sailthru", "categories": ["Marketing automation", "Email", "Personalisation"], "version": null }
  ],
  "tech_names": ["WordPress", "WordPress Block Editor", "MySQL", "PHP", "Nginx", "WordPress VIP", "Yoast SEO", "Yoast SEO Premium", "Google Tag Manager", "HSTS", "reCAPTCHA", "Sailthru"],
  "tech_slugs": ["wordpress", "wordpress-block-editor", "mysql", "php", "nginx", "wordpress-vip", "yoast-seo", "yoast-seo-premium", "google-tag-manager", "hsts", "recaptcha", "sailthru"],
  "categories": ["CMS", "Blogs", "Page builders", "Databases", "Programming languages", "Web servers", "Reverse proxies", "PaaS", "SEO", "WordPress plugins", "Tag managers", "Security", "Marketing automation", "Email", "Personalisation"],
  "scan_time_ms": 551,
  "fetched_at": "2026-09-12T15:21:42+00:00",
  "input": "techcrunch.com"
}
```

An online shop, where the interesting part is that the storefront and the content system are two
different products:

```json
{
  "url": "https://www.gymshark.com/",
  "status": "ok",
  "http_status": 200,
  "tech_count": 11,
  "tech_names": ["Contentful", "Next.js", "Shopify", "Algolia", "Afterpay", "Amazon Web Services", "React", "Amazon CloudFront", "HSTS", "Linkedin Insight Tag", "Webpack"],
  "categories": ["CMS", "JavaScript frameworks", "Web frameworks", "Web servers", "Static site generator", "Ecommerce", "Search engines", "Payment processors", "Buy now pay later", "PaaS", "CDN", "Security", "Analytics", "Miscellaneous"],
  "scan_time_ms": 465
}
```

Versions read off a run of 31 well-known sites the same day: WordPress 7.2, WordPress 7.1,
WordPress 6.9.7, Yoast SEO 27.3, Yoast SEO 25.1, jQuery 1.8.2, jQuery UI 1.12.1, Astro 6.3.7,
Nginx 1.27.5 and Sentry 9.12.0. A version appears when the page publishes one; most technologies
never do.

### Pricing

| Event | Price |
|---|---|
| Page scanned (one dataset row) | $0.01, which is $10.00 per 1,000 pages |
| Actor start | $0.00005 per GB of run memory, charged once per run |

Worked example: a list of 2,000 domains where 1,850 answer is 1,850 x $0.01 = $18.50 plus one
start event. Users on paid Apify plans pay less per page.

You pay only these event prices; Apify compute is not billed to you separately. Pages that did not
answer, were refused, timed out, answered with a challenge, answered HTTP 400 or worse, were not a
web page, or were removed by your category filter produce no rows and cost nothing. If you set a
maximum cost per run, nothing above it is ever charged: the rows that would have passed it are not
delivered, the run stops rather than reading pages nobody will be shown, and the log says how many
pages were scanned but left out so a short dataset is never mistaken for sites that failed.

### FAQ

#### Does this need a browser?

No, and that is deliberate. Everything comes out of one ordinary HTTP request: the response
headers, its cookies, the meta tags, the markup, the inline scripts and the script URLs. A
headless browser per site would be slower by an order of magnitude and would cost more to run than
this listing sells for. The trade is in **Limits** below, stated plainly.

#### How many technologies can it find?

The fingerprint set indexes **7,610**. **5,834 of them - 76.7% - are reachable from a single plain
response**, and that is the number this actor works with. The rest need a live JavaScript
environment, a DNS or certificate lookup or a second request, and are not attempted.

#### Where do the versions come from?

From the page itself: a generator meta tag, a query string on an asset (`?ver=6.9.7`), a
fingerprinted file path, a header. Nothing is guessed or inferred from release dates. In a run of
31 well-known sites, 7 of them carried at least one version - about one row in four - and
WordPress sites carried the most, because WordPress and its plugins publish theirs.

#### Why did a big brand's site come back with no row?

Some large consumer sites answer a datacenter connection with an anti-bot challenge. This actor
does not work around that: the host is backed off from, never retried through another address, and
reported in the run log. You are not charged. In a test of 40 well-known sites, 31 answered
normally, 7 answered with a challenge and 2 did not answer in time.

#### Can I scan a page other than the homepage?

Yes. Pass any URL. Product, pricing and checkout pages often reveal payment processors, A/B
testing tools and personalisation platforms that the homepage does not load.

#### Is a page with nothing detected still charged?

Yes - it is a real answer, and `status` says `no_match` so you can filter it out. It is rare: a
server header alone is usually a match, and in a run of 31 well-known sites it did not happen once.
If you only want pages that run something specific, use the category filter, which is free.

#### How fast is it?

A quarter of a second to two and a half seconds per page, and it is nearly all network: measured
on real pages, the matching itself took 11 ms on a 51 KB page, 52 ms on a 358 KB one and 176 ms on
a 1.6 MB one. A run of 40 well-known sites at eight in parallel finished in 10 seconds, 5 of them
spent fetching and matching, and used 115 MB of memory.

#### Does it respect a site's wishes?

It never works around a refusal. One page per URL, fetched as an ordinary browser-like client at a
browser's pace; a site that answers with a challenge or a rate limit is left alone rather than
retried from another address, and that row costs you nothing. Business owners can ask to be left
out entirely, and every run reads that removal list before it starts - a domain on it is never
requested at all.

It does not read `/robots.txt`. You are handing it a list of pages you already mean to look at,
one fetch each, and what comes back is what any visitor's browser would be served. Those rules
are written for crawlers that follow links across a site; this actor follows none, and crawls
nothing. Whether that suits your work is yours to judge, and the Limits section states it
plainly so that you can.

### Limits

- **JavaScript-only signals are not detected.** About 1,776 of the 7,610 technologies - roughly a
  quarter - are only identifiable from a running JavaScript environment, intercepted network
  traffic, a DNS or certificate lookup, or an extra request. Those fingerprints are skipped rather
  than guessed at. In practice this mostly costs you tools that load themselves late through a tag
  manager, and some single-page applications that render everything in the browser.
- External JavaScript files are not downloaded and read. A technology whose only trace is inside a
  bundle it fetches will not appear.
- One page per URL. Nothing is crawled, and no second page is fetched.
- 5,000 URLs per run, 90 seconds per page at most, 10 pages at a time at most.
- The first 2 MB of a page is scanned. Almost nothing real is longer.
- The first 250 script tags and 250 meta tags on a page are read. The heaviest page in a
  40-site sample carried 107 script tags, so this is a ceiling against a page built to be
  expensive rather than a limit a real site meets.
- A detection is evidence that something is present, not proof that it is in use. A leftover
  script tag looks the same as a live one.
- Absence is weaker than presence: "no analytics found" can mean a tool loaded later in the
  browser, not that the site has none.
- Nothing is cached between runs. Each run reports what the page served at that moment, which is
  also why `fetched_at` is on every row.
- Datacenter connections only. There is no proxy option and no browser option.
- **robots.txt is not read.** Every URL you pass is fetched, including one whose path a site's
  robots.txt would tell a crawler to leave alone. Nothing is crawled and no link is followed, so
  the only pages touched are the ones in your input - but if your work requires those rules to be
  applied, apply them to your list before you send it.

### Attribution

Technology detection uses the open fingerprint set from
[enthec/webappanalyzer](https://github.com/enthec/webappanalyzer) (GPL-3.0), pinned to a commit.
Locomint runs it server-side and returns results rather than redistributing the data. A small
number of additional signatures are maintained by Locomint.

### Compliance

Rows are about websites, not people. No field holds a person's name, email address or profile, and
nothing is collected beyond what the page serves to any visitor. A site that answers with a
challenge or a block is not worked around. You are responsible for using the results lawfully.

Questions, bug reports and feature requests go on this actor's Issues tab. Business owners can send
data-removal requests to info@locomint.io; that address is for removal requests only. This actor
keeps no copy of what it reads between runs.

### Other Locomint actors

- [Google Maps Scraper & Email Extractor – Business Leads](https://apify.com/locomint/google-maps-scraper): Search terms and a city in, business records with website contacts out.
- [Google Maps Scraper – Multi-City Lead Lists with Emails](https://apify.com/locomint/google-maps-multi-city-scraper): Many categories across many cities in one deduplicated run.
- [Google Maps Place Details Scraper – Bulk Place ID Lookup](https://apify.com/locomint/google-maps-place-details-scraper): Place IDs or place-page links in, full records out.
- [Website Email Scraper – Contact Details, Socials & WhatsApp](https://apify.com/locomint/website-content-contact-extractor): Contact points from website URLs you supply.
- [Website Content Crawler – Markdown for AI, Emails & Contacts](https://apify.com/locomint/website-crawler-content-contacts): A whole site as Markdown, with its contact points.
- [Bulk Email Verifier & Validator](https://apify.com/locomint/bulk-email-verifier): Checks whether addresses can receive mail.
- [Company Enrichment API – Domain to Emails, Socials & Tech](https://apify.com/locomint/company-enrichment): A domain in, its contacts and technologies out.
- [AI Crawler Checker – robots.txt Rules for GPTBot & ClaudeBot](https://apify.com/locomint/ai-crawler-checker): Which AI crawlers a site's robots.txt allows.
- [Schema Markup Validator & Generator – JSON-LD Checker](https://apify.com/locomint/schema-markup-validator): Checks and generates schema.org markup.
- [Career Site Job Scraper – Greenhouse, Lever, Ashby, Workable](https://apify.com/locomint/career-site-jobs-scraper): Open vacancies straight from an employer's own job feed.

# Actor input Schema

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

One per line, in whichever form you have: a bare domain (stripe.com), a full URL (https://www.shopify.com/) or any deeper page (https://example.com/pricing). A bare domain is read as https://. Duplicates are removed. Up to 5000 per run.

## `versions` (type: `boolean`):

Report the version where the page reveals one - WordPress 6.9.7, Angular 20.3.26, Yoast SEO 25.1. Turn it off to get names only; the price is the same either way.

## `categoriesFilter` (type: `array`):

Leave empty to get everything a page runs. Name one category per line to keep only those technologies - and a page with nothing in them produces no row and is not charged for, so "every Shopify site in this list" costs you only the Shopify sites. Spelling is matched case-insensitively against the real category list; anything unrecognised is named in the run log. Common ones: CMS, Ecommerce, Analytics, JavaScript frameworks, Web frameworks, Web servers, CDN, Hosting, PaaS, Payment processors, Tag managers, Marketing automation, CRM, Live chat, SEO, Advertising, Security, Cookie compliance, Email, Reviews, Page builders, WordPress plugins, WordPress themes, Shopify apps, Shopify themes, Databases, Programming languages, Static site generator, Artificial Intelligence.

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

How many websites to read at the same time.

## `timeoutSeconds` (type: `integer`):

A website that has not answered by then is reported in the run log and left out. It produces no row, so it costs nothing.

## Actor input object example

```json
{
  "urls": [
    "wordpress.org",
    "www.gymshark.com",
    "vercel.com"
  ],
  "versions": true,
  "concurrency": 5,
  "timeoutSeconds": 20
}
```

# Actor output Schema

## `sites` (type: `string`):

One row per URL read: every technology found with its proper name, categories and the version where the page states it, plus the same findings as names and as slugs, the category list and how long the scan took. A URL that could not be read, one that was not a web page and one your category filter emptied are each left out and cost nothing.

# 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": [
        "wordpress.org",
        "www.gymshark.com",
        "vercel.com"
    ]
};

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

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

```

## MCP server setup

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