# SaaS Technology Intelligence (`michiganpublictech/saas-tech-intel`) Actor

Detect the technology stack of company websites at scale — CMS, analytics, payments, CRM, chat, CDN, hosting, security headers and more — for sales prospecting, competitive and market research.

- **URL**: https://apify.com/michiganpublictech/saas-tech-intel.md
- **Developed by:** [Justin Hagerty](https://apify.com/michiganpublictech) (community)
- **Categories:** Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 domain analyseds

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

## SaaS Technology Intelligence: tech stack detection for company websites

Find out which technologies thousands of companies use: CMS, ecommerce platform, analytics, marketing
automation, ad pixels, CRM/ABM tools, live chat, payments, auth, CDN, hosting, JavaScript frameworks,
consent managers, monitoring and more. You give it a list of domains. You get back **one clean profile per
domain**, ready for your CRM, a spreadsheet or an AI agent.

- **225 technologies in 25 categories**, each with a confidence score, a version where one can be read, and
  the *kinds* of evidence it was matched on.
- **Company signals** from the public homepage: site name, title, description, language, canonical URL,
  company social profiles, and whether there are pricing, careers, docs, status and security/trust pages.
- An optional **web hygiene score** (0-100) built from 16 passive checks: HTTPS, HSTS, CSP, outdated
  jQuery/Bootstrap, mixed content, ad pixels without a consent manager, and more.
- **Polite by design.** It respects robots.txt, fetches a domain's pages one at a time, uses public data
  only and never collects personal data.
- **Pay per domain:** $4 per 1,000 domains. Failed domains are free.

### What can you use it for?

| Use case | How |
|---|---|
| **Build lead lists by technology** | Find every prospect running Shopify + Klaviyo, or HubSpot without a live chat, or Stripe-powered SaaS on Vercel. Filter on `tech_names`, `has_payments`, `has_chat`, `cms`... |
| **Competitor & win/loss monitoring** | Schedule a weekly run on your competitors' or customers' domains and diff the `tech_names` field to spot switches (e.g. Intercom → Zendesk). |
| **TAM sizing & market research** | Profile a list of 10k domains in one run and count adoption per category (`categories` object) to size a market or validate positioning. |
| **CRM enrichment** | Push `cms`, `ecommerce_platform`, `hosting`, `analytics` and `has_payments` into Salesforce/HubSpot company records via Make, Zapier, n8n or Clay. |
| **AI agents doing account research** | Give your agent a tool that returns a compact, structured stack and company summary for any domain. That is cheaper and more reliable than letting the agent browse. |
| **Agencies & security vendors** | Use the web hygiene score and findings (missing HSTS/CSP, jQuery < 3.5, mixed content) as an outreach hook or qualification signal. |

### Features

- **Bulk input.** Paste domains or full URLs (`https://www.stripe.com/pricing` becomes `stripe.com`).
  Input is lower-cased, internationalised domain names are converted to their ASCII (IDNA) form, and
  duplicates are removed.
- **Smart fetching.** It tries `https://domain/`, then `https://www.domain/`, then `http://domain/`, and
  follows redirects safely. Pages are capped at 2 MB and requests have short timeouts.
- **Deep scan (optional).** Also fetches up to 10 same-site pages linked from the homepage (pricing/plans,
  careers/jobs, docs/developers, about, security/trust, contact). This finds tools that only appear
  there: billing (Chargebee, Paddle), applicant tracking systems (Greenhouse, Lever, Ashby), docs
  platforms (ReadMe, Mintlify) and more. It also checks for `/.well-known/security.txt`.
- **Concurrency with good manners.** Up to 50 domains run in parallel. Pages of one domain are always
  fetched one at a time.
- **Spending-limit aware.** It never starts more domains than your remaining budget can pay for, even
  when running in parallel.
- **Category filter.** Output only the categories you care about, e.g. `["payments", "chat-support"]`.
- **Spreadsheet-friendly.** Flat fields (`tech_names`, `cms`, `ecommerce_platform`, `hosting`,
  `has_payments`, ...) export cleanly to CSV and Excel. Dataset views include *Overview*,
  *Technologies* (one row per technology), *Company* and *Hygiene findings*.
- **Proxy support (optional)** through Apify Proxy or your own proxies.

### How to use

#### In Apify Console

1. Open the Actor and paste your domains into **Domains or URLs** (one per line).
2. Optionally enable **Deep scan** or pick **categories** to output.
3. Click **Start**. Results show up in the **Output** tab as they are produced. Use the *Overview* view
   for one row per domain, or *Technologies* for one row per technology.
4. Export to CSV, Excel or JSON, or connect an integration.

#### Via the API (cURL)

Run synchronously and get the dataset items back in one call (good for up to a few hundred domains):

```bash
curl -X POST "https://api.apify.com/v2/acts/michiganpublictech~saas-tech-intel/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["apify.com", "stripe.com", "shopify.com"], "deepScan": false}'
```

For large lists, start an asynchronous run (`POST /v2/acts/michiganpublictech~saas-tech-intel/runs`) and read
the default dataset when it finishes, or set up a webhook.

#### Python (apify-client)

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("michiganpublictech/saas-tech-intel").call(
    run_input={"domains": ["apify.com", "stripe.com"], "deepScan": True, "categories": []}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["status"] != "ok":
        print(item["target"], "failed:", item["errors"])
        continue
    d = item["data"]
    print(d["domain"], d["cms"], d["hosting"], d["has_payments"], ", ".join(d["tech_names"]))
```

#### Integrations (Google Sheets, Make, Zapier, Clay, n8n)

Use Apify's built-in [integrations](https://docs.apify.com/platform/integrations) to send results where
your team works:

- **Google Sheets.** Append the *Overview* view to a sheet after every run.
- **Make / Zapier / n8n.** Trigger on "Actor run succeeded", then map `data.tech_names`, `data.cms` etc.
  into your CRM.
- **Clay.** Call the Actor from a Clay table (HTTP API / Apify integration) to enrich rows with a domain
  column.
- **Webhooks.** Get notified when a run finishes and pull the dataset.

#### Scheduling for change monitoring

Create an Apify **Schedule** (for example weekly) with the same input. Each run is a snapshot, so
comparing `tech_names` between two runs' datasets shows what was added or removed. Apify's
"Compare datasets" or a few lines of code in your integration will do it. Built-in change detection is
on the [roadmap](#roadmap).

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | array of strings | prefill `["apify.com", "stripe.com"]` | **Required.** Domains or URLs. Normalised and deduplicated. Invalid entries are reported (free) and skipped. |
| `deepScan` | boolean | `false` | Also fetch same-site pricing / careers / docs / about / security / trust / contact pages and `security.txt`. Charged as one `deep-scan` event per domain, **only** when at least one extra page was actually fetched. |
| `maxPagesPerDomain` | integer 1-10 | `4` | Maximum number of extra pages per domain in deep scan. |
| `includeHygieneFindings` | boolean | `true` | Evaluate the web hygiene rules and score (adds one small HTTP→HTTPS redirect check per domain). |
| `categories` | array of strings | all | Only output technologies in these categories (values in the table [below](#supported-technologies)). |
| `respectRobotsTxt` | boolean | `true` | Honour robots.txt (see [Ethics & compliance](#ethics--compliance)). |
| `maxConcurrency` | integer 1-50 | `10` | Number of domains processed in parallel. |
| `proxyConfiguration` | proxy object | none | Optional Apify Proxy / custom proxies. |
| `failOnSeverity` | `none`/`critical`/`high`/`medium`/`low` | `none` | CI gate: fail the run if a hygiene finding is at or above this severity. |
| `disabledRules` | array of strings | none | Hygiene rule IDs to skip (see [RULES.md](RULES.md)). Requires the custom rules feature. |
| `licenseKey` | secret string | none | Optional enterprise / prepaid key ([licensing](../../docs/licensing.md)). |

Example:

```json
{
  "domains": ["apify.com", "https://www.stripe.com/pricing", "shopify.com"],
  "deepScan": true,
  "maxPagesPerDomain": 4,
  "categories": [],
  "maxConcurrency": 10
}
```

### Output

One dataset item per domain, using the same result shape as all our Actors. The product-specific
profile is under `data`. Below is a real item produced from one of our test fixtures (a fictional
Next.js SaaS site), shortened where marked:

```json
{
  "product": "saas-tech-intel",
  "product_version": "0.1.0",
  "target": "flowbase.example",
  "status": "ok",
  "generated_at": "2026-10-11T17:46:33.935064Z",
  "score": 97,
  "risk_level": "low",
  "summary": {"critical": 0, "high": 0, "medium": 0, "low": 3, "info": 1},
  "findings": [
    {
      "rule_id": "TECH-HDR-003",
      "title": "Missing Content-Security-Policy header",
      "severity": "low",
      "kind": "runtime",
      "category": "headers",
      "location": "https://flowbase.example/",
      "evidence": {"header": "content-security-policy"},
      "recommendation": "Define a Content-Security-Policy (start with Report-Only to tune it) to limit script sources.",
      "references": ["CWE-693", "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy"],
      "confidence": 1.0,
      "fingerprint": "31a49df9b4cce583"
    }
  ],
  "data": {
    "domain": "flowbase.example",
    "outcome": "ok",
    "final_url": "https://flowbase.example/",
    "status_code": 200,
    "technologies": [
      {"name": "Google Tag Manager", "slug": "google-tag-manager", "category": "analytics", "confidence": 0.95, "version": null, "evidence": ["scriptSrc", "html"]},
      {"name": "Chargebee", "slug": "chargebee", "category": "payments", "confidence": 1.0, "version": null, "evidence": ["scriptSrc", "html"]},
      {"name": "Stripe", "slug": "stripe", "category": "payments", "confidence": 1.0, "version": null, "evidence": ["cookies", "scriptSrc", "html", "links"]},
      {"name": "Vercel", "slug": "vercel", "category": "hosting", "confidence": 1.0, "version": null, "evidence": ["headers"]},
      {"name": "Next.js", "slug": "nextjs", "category": "js-framework", "confidence": 1.0, "version": null, "evidence": ["scriptSrc", "html"]},
      {"name": "React", "slug": "react", "category": "js-framework", "confidence": 0.8, "version": null, "evidence": ["implied"], "implied_by": ["nextjs"]},
      {"name": "Ashby", "slug": "ashby", "category": "ats", "confidence": 0.7, "version": null, "evidence": ["html", "links"]}
    ],
    "categories": {"analytics": ["Google Tag Manager", "Segment"], "payments": ["Chargebee", "Stripe"], "hosting": ["Vercel"], "...": ["(shortened)"]},
    "tech_names": ["Ashby", "Calendly", "Chargebee", "G2", "Google Fonts", "Google Tag Manager", "Intercom", "LinkedIn Insight Tag", "Next.js", "OneTrust", "React", "Segment", "Sentry", "Stripe", "Tailwind CSS", "Vercel"],
    "tech_count": 16,
    "has_payments": true,
    "has_chat": true,
    "cms": null,
    "ecommerce_platform": null,
    "analytics": ["Google Tag Manager", "Segment"],
    "hosting": "Vercel",
    "cdn": null,
    "company": {
      "name": "Flowbase",
      "title": "Flowbase — Workflow automation for revenue teams",
      "description": "Flowbase automates your revenue workflows. Start free.",
      "canonical_url": "https://flowbase.example/",
      "language": "en",
      "social_profiles": {
        "linkedin": "https://www.linkedin.com/company/flowbase-example",
        "x": "https://x.com/flowbase",
        "github": "https://github.com/flowbase"
      },
      "pages": {
        "pricing": "https://flowbase.example/pricing", "careers": "https://flowbase.example/careers",
        "docs": "https://docs.flowbase.example/", "about": "https://flowbase.example/about",
        "security": "https://flowbase.example/security", "trust": null,
        "contact": "https://flowbase.example/contact-sales", "status": "https://status.flowbase.example/",
        "blog": "https://flowbase.example/blog"
      },
      "has_pricing_page": true,
      "has_careers_page": true,
      "has_docs": true,
      "has_status_page": true,
      "has_security_or_trust_page": true,
      "sitemaps": ["https://flowbase.example/sitemap.xml"],
      "has_sitemap": true
    },
    "pages_scanned": 3,
    "scanned_urls": ["https://flowbase.example/", "https://flowbase.example/pricing", "https://flowbase.example/careers"],
    "hygiene_checked": true,
    "fetched_at": "2026-10-11T17:46:33+00:00",
    "notes": ["Deep scan: https://docs.flowbase.example/: HTTP 404", "Deep scan: https://flowbase.example/about: HTTP 404"]
  },
  "errors": [],
  "disclaimer": "Technology detections are heuristic, based only on publicly served signals (HTML, headers, cookies) from a plain HTTP fetch. ..."
}
```

**Field notes**

- `status`: `ok` for an analysed (and charged) domain. `failed` for a domain that produced no profile
  (not charged). `data.outcome` gives the reason: `failed` (unreachable or HTTP error),
  `skipped_robots_txt` or `invalid_input`. `errors` explains it in plain words.
- `technologies[].confidence`: 0-1 signal strength (see [How detection works](#how-detection-works)).
- `technologies[].evidence`: the **types** of signals that matched (`headers`, `cookies`, `meta`,
  `scriptSrc`, `html`, `links`, `implied`). The page content itself is never copied.
- `cms`, `ecommerce_platform`, `hosting`, `cdn`: the highest-confidence technology in that category, or
  `null`. `analytics` lists every analytics or tag-management tool found.
- `score` / `risk_level` / `findings`: the **web hygiene score** and its findings (see below). With
  `includeHygieneFindings: false` there are no findings and `data.hygiene_checked` is `false`.
- `company.has_sitemap` refers to sitemaps declared in robots.txt.

The run's key-value store also holds `OUTPUT` (run summary) and `REPORT.md` (human-readable report).

### Supported technologies

**225 technologies in 25 categories** (full list with detection signals: [TECHNOLOGIES.md](TECHNOLOGIES.md)).

| Category | `categories` value | Technologies |
|---|---|---|
| CMS | `cms` | Contentful, Craft CMS, Drupal, Framer, Ghost, HubSpot CMS, Hugo, Joomla, Prismic, Sanity, Squarespace, Storyblok, Webflow, Wix, WordPress |
| Ecommerce | `ecommerce` | BigCommerce, Ecwid, Magento, PrestaShop, Salesforce Commerce Cloud, Shopify, WooCommerce |
| Analytics & tag management | `analytics` | Adobe Analytics, Amplitude, Fathom, FullStory, Google Analytics, Google Tag Manager, Heap, Hotjar, Matomo, Microsoft Clarity, Mixpanel, Pendo, Plausible, PostHog, Segment, Snowplow, Vercel Web Analytics |
| Marketing automation | `marketing-automation` | ActiveCampaign, Braze, ConvertKit (Kit), Customer.io, Drip, HubSpot, Iterable, Klaviyo, Mailchimp, Marketo, Salesforce Pardot |
| Advertising pixels | `advertising` | Google Ads, Google Publisher Tag, LinkedIn Insight Tag, Meta Pixel, Microsoft Advertising (UET), Pinterest Tag, Quora Pixel, Reddit Pixel, TikTok Pixel, X (Twitter) Pixel |
| CRM / ABM / visitor identification | `crm-abm` | 6sense, Clearbit, Demandbase, Leadfeeder (Dealfront), RB2B, Salesforce, ZoomInfo |
| Live chat & support | `chat-support` | Crisp, Drift, Freshchat, Freshdesk, Front Chat, Gorgias, Help Scout, HubSpot Chat, Intercom, LiveChat, Olark, Qualified, Tawk.to, Zendesk |
| Payments & billing | `payments` | Adyen, Braintree, Chargebee, Klarna, Lemon Squeezy, Paddle, PayPal, Recurly, Shop Pay, Stripe |
| Authentication / identity | `auth` | Amazon Cognito, Auth0, Clerk, Firebase Authentication, Okta, WorkOS |
| CDN / edge | `cdn` | Akamai, Amazon CloudFront, Bunny CDN, cdnjs, Cloudflare, Fastly, Google Cloud CDN, jsDelivr, unpkg |
| Hosting / PaaS / cloud | `hosting` | Amazon Web Services, Cloudflare Pages, Firebase Hosting, Fly.io, GitHub Pages, Google Cloud, Heroku, Kinsta, Microsoft Azure, Netlify, Pantheon, Render, Vercel, WP Engine |
| JavaScript frameworks & libraries | `js-framework` | Alpine.js, Angular, AngularJS, Astro, Ember.js, Gatsby, htmx, jQuery, Next.js, Nuxt, Preact, React, Remix / React Router, Svelte, SvelteKit, Vue.js |
| UI / CSS frameworks | `ui-framework` | Bootstrap, Bulma, Chakra UI, Foundation, MUI (Material UI), Tailwind CSS |
| A/B testing & feature flags | `ab-testing` | AB Tasty, Google Optimize (legacy), LaunchDarkly, Mutiny, Optimizely, Statsig, VWO |
| Consent management (CMP) | `consent` | Cookiebot, CookieYes, Didomi, iubenda, Ketch, OneTrust, Osano, Termly, TrustArc, Usercentrics |
| Monitoring / error tracking / RUM | `monitoring` | Bugsnag, Datadog RUM, Dynatrace, Elastic APM RUM, LogRocket, New Relic, Raygun, Rollbar, Sentry |
| Site search | `search` | Algolia, Coveo, Elastic (Site / App Search), Typesense |
| Video | `video` | Loom, Mux, Vidyard, Vimeo, Wistia, YouTube |
| Meeting scheduling | `scheduling` | Cal.com, Calendly, Chili Piper, HubSpot Meetings, SavvyCal |
| Forms & surveys | `forms` | Formstack, Google Forms, Gravity Forms, HubSpot Forms, Jotform, Tally, Typeform |
| Reviews & social proof | `reviews` | Capterra, G2, Senja, Trustpilot, Yotpo |
| Applicant tracking (careers) | `ats` | Ashby, BambooHR, Greenhouse, Lever, Personio, Recruitee, SmartRecruiters, Teamtailor, Workable, Workday Recruiting |
| Documentation platforms | `docs` | Docusaurus, GitBook, Mintlify, Nextra, ReadMe, Redoc, Swagger UI |
| Fonts & icons | `fonts` | Adobe Fonts, Bunny Fonts, Font Awesome, Google Fonts |
| Security / WAF / bot protection | `security` | Akamai Bot Manager, Cloudflare Bot Management, Cloudflare Turnstile, DataDome, hCaptcha, HUMAN (PerimeterX), Imperva (Incapsula), reCAPTCHA, Sucuri |

### How detection works

Every technology is described by a **fingerprint** in a versioned data file (`fingerprints.json`).
Each fingerprint lists public signals that reveal the technology:

```json
{
  "name": "Next.js",
  "slug": "nextjs",
  "category": "js-framework",
  "website": "https://nextjs.org",
  "signals": {
    "headers":   {"x-powered-by": "^Next\\.js(?: (?P<version>\\d+(?:\\.\\d+){0,2}))?"},
    "cookies":   {},
    "scriptSrc": ["/_next/static/"],
    "html":      ["\\b__NEXT_DATA__\\b", "/_next/static/"],
    "meta":      {},
    "links":     []
  },
  "implies": ["react"]
}
```

| Signal type | Matched against | Confidence added per match |
|---|---|---|
| `meta` | `<meta name/property>` content (e.g. `generator`) | 0.70 |
| `scriptSrc` | `<script src>` URLs | 0.60 |
| `headers` | HTTP response headers (`""` = header present) | 0.50 |
| `cookies` | Cookie names set by the server (`name*` = prefix) | 0.40 |
| `html` | Raw HTML: inline snippets, asset URLs, attributes | 0.35 |
| `links` | `<a>`, `<link>` and `<iframe>` URLs | 0.35 |

- **Confidence** is the sum over every matched signal, capped at 1.0. One weak hint (for example a
  single HTML pattern) gives about 0.35. A script URL plus a cookie plus an inline snippet gives 1.0.
  Filter on `confidence >= 0.5` if you want only strong detections.
- **Versions** are pulled from regex groups named `version`, such as WordPress's
  `<meta name="generator" content="WordPress 6.4.2">` or `jquery.min.js?ver=1.12.4`.
- **Implications** add technologies that must be present: Next.js implies React, WooCommerce implies
  WordPress, Nextra implies Next.js which implies React. Implied technologies get 80% of the implying
  technology's confidence and evidence `implied`.
- **Speed and safety.** All regexes are compiled once and are case-insensitive. Each one has a literal
  prefilter (a fast substring check), so a 2 MB page is scanned in a fraction of a second. HTML is
  scanned in a bounded window (the first 600k and last 200k characters). The test suite rejects
  patterns that could cause catastrophic backtracking.
- With deep scan, signals from all fetched pages are combined, but headers and hygiene checks use the
  homepage.

#### Web hygiene score

When `includeHygieneFindings` is on, 16 passive rules (prefix `TECH`) run against the homepage response.
They cover transport security (HTTPS, HTTP→HTTPS redirect, mixed content), security headers (HSTS,
CSP, nosniff, clickjacking protection, version disclosure), cookie flags, outdated client libraries
(jQuery < 3.5, AngularJS 1.x, old Bootstrap), CMS version exposure, ad pixels without a consent manager,
and `security.txt` (deep scan only). The score starts at 100 and loses points per finding, weighted by
severity, using the same formula as all our Actors. The full catalog is in [RULES.md](RULES.md).

These are observations from a normal page load, not a security audit or penetration test.

### Ethics & compliance

- **robots.txt is respected by default.** We fetch `/robots.txt` once per site and follow the group for
  our product token (`saas-tech-intel`) or `*`. The most specific matching rule wins, with `*` and `$`
  wildcards supported (RFC 9309). A 5xx robots.txt is treated as "disallow everything". Disallowed
  domains are listed in the output with `data.outcome: "skipped_robots_txt"` and are **not charged**.
- **Public data only.** We make the same requests a browser makes when someone opens the homepage
  (plus a few linked pages with deep scan). We never log in, submit forms, run exploits or scan ports.
- **No personal data.** We deliberately **do not extract email addresses, phone numbers, people's names
  or personal social profiles** (for example `linkedin.com/in/...`). Only *company* profiles are
  reported. Free-text fields (title, description) are cleaned of anything that looks like an email
  address or phone number. This keeps the output GDPR/CCPA-friendly and safe to store in a CRM. If you
  need contacts, use a dedicated, consent-aware data provider.
- **Rate limits.** Pages of one domain are fetched sequentially with short timeouts and a 2 MB size cap.
  Every request identifies itself honestly with a descriptive User-Agent
  (`saas-tech-intel/<version> (+https://apify.com)`).
- **Safe networking.** All requests go through our hardened HTTP client. It blocks private and internal
  addresses (SSRF protection) and re-checks every redirect.

You are responsible for using the results lawfully, for example under the e-privacy and marketing rules
that apply to your outreach.

### Pricing

Pay per event: you only pay for results, and Apify bills you directly.

| Event | When it is charged | Price | Per 1,000 |
|---|---|---|---|
| `domain-analyzed` | A technology profile is produced for one domain | $0.004 | **$4 per 1,000 domains** |
| `deep-scan` | Deep scan fetched at least one extra page for that domain | $0.006 | $6 per 1,000 domains |

- **Not charged:** unreachable domains, HTTP errors, robots.txt-disallowed domains and invalid input.
- Example: 10,000 domains without deep scan cost **$40**. With deep scan (most sites have at least one
  matching page) it is about **$100**.
- **Spending cap.** Set *Maximum cost per run* in Apify. The Actor checks your remaining budget before
  starting each domain, never starts more parallel domains than the budget covers, and stops cleanly when
  it is reached. A note in the run's `OUTPUT` says how many domains were not analysed.
- Platform usage (compute) is included in the event price.

### Plans & licensing

Apify Store users need no license: pay per event and you get the full product, with up to 10,000
domains per run (your Apify "maximum cost per run" still applies). Larger runs (25,000–100,000 domains
per run on Pro/Team/Enterprise), custom rule sets (`disabledRules`),
white-label reports and invoiced/prepaid billing come with Pro, Team and Enterprise license keys. Pass
the key in the secret `licenseKey` field. See [docs/licensing.md](../../docs/licensing.md) for plans
and how keys work.

### Limitations

- **No browser in v1.** Pages are fetched with plain HTTP and JavaScript is not executed. Tools injected
  only at runtime (for example through Google Tag Manager, or consent banners loaded late) and
  client-rendered single-page apps may be under-detected. In that case the tag manager itself is still
  detected.
- **Bot protection.** Sites behind aggressive bot protection (Cloudflare challenges, Akamai, DataDome)
  may return 403/503. These domains are reported as `failed` and not charged. The protection vendor is
  often identifiable from headers. A proxy can help in some cases.
- **Heuristics.** Detections come from public signals and can be wrong. For example, a marketing page
  that merely *links* to a vendor's site is not counted, but an embedded widget is. Use `confidence`.
- **Homepage-centric.** Without deep scan only the homepage is analysed. Server-side-only tools (a CRM
  with no web snippet, backend databases) are invisible from the outside.
- **No DNS-based signals** (MX/SPF/TXT records) in v1.

### FAQ

**Do I need to give a full URL?** No. `stripe.com`, `www.stripe.com` and `https://stripe.com/pricing`
are all accepted and deduplicated to `stripe.com`.

**Why is a domain "failed" when it works in my browser?** It probably blocks non-browser clients (bot
protection) or only answered on a different host. Check `errors`. Failed domains are free.

**Can I get only payment and chat tools?** Yes: `"categories": ["payments", "chat-support"]`.

**How accurate is it?** Strong detections (confidence ≥ 0.6) come from vendor-specific script URLs,
cookies or headers and are very reliable. Low-confidence ones (0.35) come from a single HTML hint and are
worth a quick check.

**Will the site owner see my requests?** They see a few normal requests from our User-Agent, like any
crawler. Nothing is posted, and no logins or forms are touched.

**Does it extract emails or phone numbers?** No, on purpose. See [Ethics & compliance](#ethics--compliance).

**Can I add a technology?** Yes. Open an issue with the vendor's script URL, cookie or header. The
fingerprint file is data-driven and is updated regularly.

### Roadmap

- **Headless browser mode** (opt-in) to detect tools injected at runtime and JavaScript-rendered sites.
- **Change monitoring:** built-in comparison with the previous run, with "added/removed technology"
  events and alerts.
- DNS-based signals (email provider, SPF/DMARC, verification TXT records).
- More fingerprints (vertical SaaS, regional payment and consent providers).

### Changelog & support

See the [Changelog](CHANGELOG.md). Found a false positive or missing technology? Open an issue on the
Actor page and include the domain and what you expected.

# Changelog

This Actor's version history is a separate document: https://apify.com/michiganpublictech/saas-tech-intel/changelog.md

# Actor input Schema

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

Company domains to profile, e.g. `stripe.com`. Full URLs (`https://www.stripe.com/pricing`) are accepted and normalised to the domain; duplicates are removed. Each successfully analysed domain is one billable result.

## `deepScan` (type: `boolean`):

Also fetch up to `maxPagesPerDomain` same-site pages linked from the homepage (pricing/plans, careers/jobs, docs/developers, about, security/trust, contact) to find technologies that only appear there (payments, ATS, docs platforms...). Also checks /.well-known/security.txt. Charged as one extra `deep-scan` event per domain, only when at least one extra page was fetched.

## `maxPagesPerDomain` (type: `integer`):

Upper bound on additional pages fetched per domain when deep scan is on.

## `includeHygieneFindings` (type: `boolean`):

Evaluate passive web-hygiene rules (HTTPS, HSTS, CSP, outdated jQuery, mixed content, ad pixels without a consent manager...) and compute the web hygiene score. Adds one small request per domain (HTTP→HTTPS redirect check).

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

Restrict the `technologies` list to these categories. Leave empty for all. Valid values: cms, ecommerce, analytics, marketing-automation, advertising, crm-abm, chat-support, payments, auth, cdn, hosting, js-framework, ui-framework, ab-testing, consent, monitoring, search, video, scheduling, forms, reviews, ats, docs, fonts, security.

## `respectRobotsTxt` (type: `boolean`):

Fetch /robots.txt once per site and skip pages it disallows for our crawler (User-agent `saas-tech-intel` or `*`). Skipped domains are reported and not charged.

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

How many domains are processed in parallel. Pages of the same domain are always fetched one at a time.

## `proxyConfiguration` (type: `object`):

Optional. Route requests through Apify Proxy or your own proxies (helps with sites that rate-limit datacenter IPs). Not needed for most sites.

## `failOnSeverity` (type: `string`):

CI gate: mark the run as failed if any hygiene finding is at or above this severity. `none` never fails.

## `disabledRules` (type: `array`):

Hygiene rule IDs to skip, e.g. `TECH-HDR-003`. Requires a plan with the custom rules feature; ignored with a note otherwise.

## `licenseKey` (type: `string`):

Optional enterprise / prepaid license key. Not needed for normal pay-per-event use on Apify.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "stripe.com"
  ],
  "deepScan": false,
  "maxPagesPerDomain": 4,
  "includeHygieneFindings": true,
  "respectRobotsTxt": true,
  "maxConcurrency": 10,
  "failOnSeverity": "none"
}
```

# Actor output Schema

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

One report per target: score, risk level, findings and product data.

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

Plan, per-target scores and billing summary for the run (OUTPUT record).

## `reportMarkdown` (type: `string`):

Human-readable report of all findings (REPORT.md).

## `reportSarif` (type: `string`):

SARIF 2.1.0 report for GitHub code scanning and other CI tools (REPORT.sarif).

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

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

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

```

## MCP server setup

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

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/2OhGd8jW6CphKOniN/builds/fWpG1L20qzWB8EhTW/openapi.json
