Website Tech Stack Change Detector avatar

Website Tech Stack Change Detector

Pricing

from $8.50 / 1,000 domains

Go to Apify Store
Website Tech Stack Change Detector

Website Tech Stack Change Detector

Compare public submitted-page HTML and response-header signatures with a technology baseline you supply. Returns candidate additions, technologies not observed now, evidence provenance, confidence, gaps, stable IDs, and a conservative verification action for up to 100 websites.

Pricing

from $8.50 / 1,000 domains

Rating

0.0

(0)

Developer

Tim Zinin

Tim Zinin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

18 days ago

Last modified

Share

Website Tech Stack Change Detector — evidence-backed candidate changes

Compare a technology baseline you already hold with a fresh observation of a public website page. For each submitted URL, the Actor checks the initial HTML and public HTTP response headers for a curated catalog of CMS, ecommerce, analytics, marketing, support, framework, payments, hosting, and font signatures. It then returns what was observed now, what differs from your supplied baseline, which evidence type supports each current match, how confident the comparison is, what remains unknown, and what should be verified next.

This is not a black-box claim that a company installed or removed a vendor. It is a bounded change lead for account research, migration discovery, audits, portfolio monitoring, and data enrichment. A missing signature on one served page is reported as “not observed now,” never as proof that the technology was uninstalled.

Website Tech Stack Change Detector compares a buyer baseline with current public-page evidence and returns candidate changes, confidence, gaps, and a review action

The product in one sentence

Give the Actor 1–100 URLs, optionally followed by | and a comma-separated previous stack; receive one review-ready current observation or candidate comparison per successful URL, plus explicit unbilled failure rows and a machine-readable run summary.

The central distinction is:

  • Supported by the result: “WordPress, Nginx, Google Tag Manager, and Google Fonts signatures were observed in this submitted page response. jQuery was in the buyer-supplied baseline but was not observed on this response.”
  • Not supported by the result: “The company removed jQuery, migrated its whole site, cancelled a contract, has a replacement budget, wants an agency, or approved automated outreach.”

That boundary is why every result includes provenance, confidence reasons, explicit gaps, a recommended verification action, and safeToAutomate:false.

Who this is for

Small web and migration agencies

Monitor a permitted list of client, prospect, or portfolio sites for candidate platform movement. An observed Shopify signature that was absent from your previous baseline can move an account into a manual research queue. A previously supplied WordPress name that is not observed now can trigger a browser check or a conversation with an existing client. Neither should trigger a migration claim by itself.

Use the output to prepare a precise question: “Our latest public-page check did not observe the WordPress signature stored in our baseline. Has the site changed, or is WordPress now hidden behind rendering/CDN behavior?” That is more defensible than telling the buyer you know a migration occurred.

Marketing operations and RevOps

Attach change evidence to account records without throwing away lineage. entityId provides a stable website join key; baselineId identifies the normalized buyer-supplied comparison set; observationId identifies the current response observation; and eventId identifies this exact comparison. Use those IDs to deduplicate events in a warehouse or workflow.

Candidate additions can prioritize research into newly visible analytics, ecommerce, CRM, chat, or support tooling. Candidate non-observations can identify records that may be stale. The Actor does not validate domain ownership, contract status, budget, purchase intent, contact details, or legal basis for outreach.

Ecommerce consultants

Compare public storefront, payments, analytics, email, chat, and hosting signatures with a baseline from a prior audit. A current Shopify or WooCommerce signature is a technical observation from the submitted page. It is not proof of merchant-of-record structure, payment processing, subscription status, order volume, or commercial relationship with the vendor.

Developers and technology auditors

Use the Actor as a lightweight first-pass diff before a browser-capable or authenticated audit. It is intentionally fast: no browser, no proxy, no login, no subpage crawl, and no JavaScript execution. This makes it useful for scheduled reconnaissance and also defines what it can miss.

Portfolio, market, and competitive researchers

Run a controlled domain set on a schedule, store the current technologies array as the next baseline, and review candidate changes with the evidence and gaps retained. The Actor itself is stateless; you own the history and decide retention, comparison cadence, and escalation policy.

What the current catalog can observe

The catalog contains more than 50 public fingerprints across these groups:

CategoryExamples
CMSWordPress, Drupal, Joomla, Wix, Squarespace, Webflow, Ghost, Tilda, Framer
EcommerceShopify, WooCommerce, Magento, BigCommerce, Salesforce Commerce, PrestaShop
Analytics and tagsGoogle Analytics 4, Universal Analytics, Google Tag Manager, Meta Pixel, Hotjar, Segment, Mixpanel, Amplitude, Plausible, Microsoft Clarity
Marketing and CRMHubSpot, Marketo, Salesforce Pardot, Mailchimp, Klaviyo
Chat and supportIntercom, Drift, Zendesk, Crisp, Tawk.to, Calendly
FrameworksNext.js, Nuxt, Gatsby, Angular, jQuery
PaymentsStripe, PayPal
Hosting and CDNCloudflare, Vercel, Netlify, AWS CloudFront, Fastly, GitHub Pages, Nginx, Apache
Fonts and supporting assetsGoogle Fonts, Font Awesome

The catalog is curated, not exhaustive. An empty match means “no catalog signature was observed in this response,” not “the site uses no technology.” Every successful row carries SIGNATURE_CATALOG_NOT_EXHAUSTIVE, SUBPAGES_NOT_SCANNED, and other relevant gaps.

Five-stage workflow

Evidence-first workflow from strict URL and baseline input through guarded fetching, signature provenance, conservative comparison, and review-ready output

  1. Strict input — each item is either a public hostname/HTTP(S) URL or that URL followed by a pipe and previous technology names. Blank values, non-strings, embedded credentials, unsupported schemes, oversized values, empty baselines, unsafe concurrency, and lists above 100 are rejected. Fragments are removed. Equivalent normalized URLs with the same case-insensitive baseline set are processed once, even if the baseline order differs.
  2. Guarded fetch — the Actor resolves the hostname, rejects private/loopback/link-local/metadata and reserved addresses, pins the verified public address set into the actual connection, and repeats the guard for every redirect. It reads the site’s public User-agent: * robots rules, accepts HTML/XHTML, follows at most five redirects, waits at most 20 seconds, and caps content at 600 KB.
  3. Evidence collection — catalog matches retain technology, category, evidence type, and any contributing response-header name. Raw header values and raw page HTML are not emitted.
  4. Conservative comparison — current technology names are compared case-insensitively with the supplied baseline. Current-only names become candidate additions. Baseline-only names become candidate non-observations. Neither is upgraded into an installation/removal fact.
  5. Decision-ready output — the row adds stable IDs, timestamp, freshness, coverage, confidence, reasons, gaps, failure/retry state, priority, and a recommended next action. Automation remains review-first.

Input formats

FieldRequiredContract
itemsyesArray of 1–100 strings. Use example.com for a current observation or example.com|WordPress,jQuery for comparison. URL portion max 2,048 characters; whole item max 4,096; at most 100 previous technology names of at most 100 characters each.
maxConcurrencynoInteger 1–20. Default 10. Lower it for sites with strict rate limits.

Current observation only:

{
"items": ["shopify.com"],
"maxConcurrency": 1
}

Compare two sites with baselines:

{
"items": [
"wordpress.org|WordPress,jQuery",
"https://shopify.com/|Shopify,Cloudflare"
],
"maxConcurrency": 2
}

Mix observations and comparisons:

{
"items": [
"https://example.com/",
"https://example.org/pricing|WordPress,HubSpot",
"vercel.com|Next.js,Vercel"
],
"maxConcurrency": 3
}

The path is retained and only that submitted page is checked. A fragment such as #pricing is removed because fragments are not sent in HTTP requests. example.com|WordPress,jQuery and EXAMPLE.com|jquery,wordpress normalize to the same paid request and are processed once.

What a successful comparison row contains

The following is a shortened representative shape for wordpress.org|WordPress,jQuery. IDs and timestamps are illustrative; always use your run output.

{
"input": "wordpress.org|WordPress,jQuery",
"domain": "wordpress.org",
"requestedUrl": "https://wordpress.org/",
"finalUrl": "https://wordpress.org/",
"found": true,
"httpStatus": 200,
"partial": false,
"checkedAt": "2026-08-11T00:00:00.000Z",
"observedAt": "2026-08-11T00:00:00.000Z",
"technologiesCount": 4,
"technologies": [
"WordPress",
"Google Tag Manager",
"Nginx",
"Google Fonts"
],
"previousStack": ["WordPress", "jQuery"],
"hasPreviousBaseline": true,
"candidateAddedTechnologies": [
"Google Tag Manager",
"Nginx",
"Google Fonts"
],
"candidateNotObservedTechnologies": ["jQuery"],
"candidateChangeObserved": true,
"comparisonStatus": "CANDIDATE_CHANGE_OBSERVED",
"changeSemantics": "candidate_change_between_buyer_supplied_baseline_and_current_submitted_page_observation",
"technologyEvidence": [
{
"technology": "WordPress",
"category": "CMS",
"evidenceTypes": ["site_scoped_html_signature"],
"headerNames": [],
"evidenceOrigin": "submitted_public_website"
},
{
"technology": "Nginx",
"category": "Hosting/CDN",
"evidenceTypes": ["public_response_header"],
"headerNames": ["server"],
"evidenceOrigin": "submitted_public_website"
}
],
"coverageScore": 85,
"coverageBand": "high",
"confidenceScore": 60,
"confidenceBand": "medium",
"confidenceReasons": [
"4 current technology signature(s) were observed on the submitted page response.",
"The comparison baseline was supplied by the buyer and was not independently observed by this run.",
"A missing current-page signature is a verification lead, not proof that a technology was removed."
],
"dataGaps": [
"BUYER_SUPPLIED_BASELINE_NOT_INDEPENDENTLY_VERIFIED",
"BASELINE_TIMESTAMP_NOT_PROVIDED",
"CLIENT_SIDE_TECHNOLOGY_NOT_RENDERED",
"SUBPAGES_NOT_SCANNED",
"SIGNATURE_CATALOG_NOT_EXHAUSTIVE",
"NON_OBSERVATION_DOES_NOT_PROVE_REMOVAL",
"TECHNOLOGY_VERSION_NOT_DETERMINED",
"VENDOR_RELATIONSHIP_AND_PURCHASE_INTENT_NOT_PROVEN"
],
"recommendedAction": "VERIFY_CANDIDATE_CHANGE_WITH_BROWSER_OR_ADDITIONAL_PAGES",
"actionPriority": "high",
"safeToAutomate": false,
"failureType": null,
"retryable": false
}

Compatibility fields

Existing integrations can continue reading these fields:

  • input, domain, found, httpStatus, checkedAt;
  • technologies, stackHash, and previousStack;
  • added and removed.

Their semantics are now explicit:

  • added is an alias of candidateAddedTechnologies: catalog signatures observed now whose names were absent from the supplied baseline;
  • removed is a compatibility alias of candidateNotObservedTechnologies: supplied names without a current submitted-page signature. It is not proof of uninstallation;
  • bare URL input has no baseline, so both arrays stay empty. The Actor never pretends that every current technology is “added” when you did not provide a previous state.

New integrations should use comparisonStatus, the two candidate... arrays, changeSemantics, technologyEvidence, and the decision fields.

Comparison statuses

StatusMeaningSuggested handling
NO_BASELINE_PROVIDEDCurrent observation only.Store technologies as a future baseline if appropriate.
NO_CATALOG_CHANGE_OBSERVEDCurrent catalog names match the supplied names case-insensitively.Keep monitoring; do not infer that the entire site is unchanged.
CANDIDATE_CHANGE_OBSERVEDAt least one current-only or baseline-only name exists.Verify with a browser, more pages, or the site owner before acting.
PARTIAL_OBSERVATION_REVIEW_REQUIREDThe page hit the byte cap; the comparison uses partial content.Treat all non-observations as weak and re-check with a deeper method.
NO_SUCCESSFUL_CURRENT_OBSERVATIONFetch, policy, content-type, DNS, timeout, or HTTP failure prevented comparison.Use failureType and retryable; no change claim was made.

Evidence types

technologyEvidence distinguishes how each current signature was observed:

  • public_html_signature — a catalog pattern appeared in served HTML;
  • final_url_signature — a catalog pattern appeared in the final URL after guarded redirects;
  • public_response_header — a public response-header pattern matched; output retains only the header name, not the raw value;
  • site_scoped_html_signature — a custom rule matched a path belonging to the checked site, rather than a third-party embed.

Evidence provenance explains the current observation. It does not verify the historical baseline. If a baseline came from a different crawler, page, timestamp, region, cookie state, or catalog, differences can reflect methodology rather than a real site change.

Coverage, confidence, and gaps

coverageScore describes the completeness of this bounded page read, not coverage of the entire company stack. A complete HTML response scores higher than a response truncated at the byte cap.

confidenceScore describes how defensible the comparison is under those limits. Candidate non-observations reduce confidence because absence is weaker than observed evidence. Strong response-header or site-scoped evidence can support a candidate addition, but still does not prove a contract or installation across the whole site.

confidenceReasons explains the score in plain language. dataGaps always states the relevant unknowns. Keep both when exporting data; dropping them converts a bounded observation into an unsupported assertion.

  • STORE_OBSERVATION_AS_FUTURE_BASELINE — no baseline was supplied; retain this observation if your policy permits and use it for a later comparison.
  • RETAIN_BASELINE_AND_SCHEDULE_NEXT_OBSERVATION — no catalog-name difference was observed; keep the monitoring cadence without declaring the site unchanged.
  • VERIFY_CANDIDATE_CHANGE_WITH_BROWSER_OR_ADDITIONAL_PAGES — candidate additions or non-observations exist; validate them before CRM mutation, outreach, migration claims, or vendor decisions.
  • RETRY_AFTER_SOURCE_RECOVERY — the failure appears transient, such as timeout, DNS outage, HTTP 429, or server error.
  • REVIEW_INPUT_OR_POLICY_BOUNDARY — correct an invalid/blocked URL or respect the recorded policy boundary before retrying.

Pricing

This Actor uses pay-per-event pricing: one start event per run and one result-found event for each successfully observed page delivered to the Dataset. Failed fetch/policy rows use the zero-price default Dataset event. A run-start event may still be charged even when every submitted site fails.

Current tier prices recorded for the Actor are:

Account tierStart eventSuccessful page result100 successful pages1,000 successful pages
FREE$0.00500$0.01000$1.00500$10.00500
BRONZE$0.00475$0.00950$0.95475$9.50475
SILVER$0.00450$0.00900$0.90450$9.00450
GOLD$0.00425$0.00850$0.85425$8.50425
PLATINUM$0.00410$0.00820$0.82410$8.20410
DIAMOND$0.00400$0.00800$0.80400$8.00400

Examples assume every submitted page succeeds and exclude any future platform pricing change. Your Apify Store pricing panel and run receipt are authoritative for the tier and prices applied to your account.

Billing and budget safety

The runtime treats Dataset delivery and result-found as one linked SDK operation. For a monetized run it requires the exact linked receipt: the named result event plus the zero-price default Dataset item event. A missing or unexpected receipt count is ambiguous; OUTPUT sets replaySafe:false, the run fails, and automatic retry is discouraged until an operator checks the Dataset and charged events.

The budget check and linked delivery are serialized across concurrent workers. This prevents two workers from both seeing the same remaining budget before either charge settles. If the buyer’s cap is reached before all unique items are delivered, the Actor stops paid work and writes one free advisory row when pricing permits. If the last requested result exactly reaches the cap, the run is complete and no false “remaining work” advisory is emitted.

Run summary in OUTPUT

The default Key-value store contains OUTPUT with:

{
"ok": true,
"partial": false,
"requested": 2,
"processed": 2,
"delivered": 2,
"errors": 0,
"advisoryRows": 0,
"stoppedForBudget": false,
"replaySafe": true,
"resultEventName": "result-found"
}

requested is the number of unique normalized URL/baseline-set requests after deduplication. processed counts written result and failure rows, not skipped work. delivered counts successful paid observations. errors counts explicit unbilled failure rows. partial means not every unique request was processed, usually because of a budget or ambiguous delivery stop.

Failure and recovery behavior

Failure classTypical causeRetryable
INPUT_OR_POLICY_BLOCKInvalid/unsafe URL, private destination, unsupported content type, robots restrictionno
SOURCE_TIMEOUTRequest exceeded 20 secondsyes
SOURCE_UNAVAILABLEDNS resolution failedyes
SOURCE_HTTP_TRANSIENTHTTP 429 or 5xxyes
SOURCE_HTTP_TERMINALOther HTTP 4xxno
SOURCE_OR_PROCESSING_FAILUREOther bounded fetch/processing failurereview

A failed current observation has comparisonStatus:NO_SUCCESSFUL_CURRENT_OBSERVATION, empty candidate arrays, zero confidence, explicit gaps, and no removal claim. It is not billed with result-found.

Pricing misconfiguration fails closed. If the paid event is missing, or ordinary Dataset writes would themselves be billable, the Actor refuses to deliver results under a pricing contract that would contradict the Store description.

Call through the Apify API

Keep your token in a secret manager and pass it in the Authorization header.

Start a run and wait up to 120 seconds:

curl -X POST \
"https://api.apify.com/v2/acts/zinin~tech-stack-change-detector/runs?waitForFinish=120" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"items": ["wordpress.org|WordPress,jQuery"],
"maxConcurrency": 1
}'

Run synchronously and return Dataset items:

curl -X POST \
"https://api.apify.com/v2/acts/zinin~tech-stack-change-detector/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"items":["shopify.com|Shopify,Cloudflare"],"maxConcurrency":1}'

Retrieve OUTPUT after a run using the run’s defaultKeyValueStoreId:

curl \
"https://api.apify.com/v2/key-value-stores/STORE_ID/records/OUTPUT" \
-H "Authorization: Bearer $APIFY_TOKEN"

JavaScript client

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('zinin/tech-stack-change-detector').call({
items: [
'wordpress.org|WordPress,jQuery',
'shopify.com|Shopify,Cloudflare',
],
maxConcurrency: 2,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems({ clean: true });
const output = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('OUTPUT');
for (const row of items) {
if (row.found && row.candidateChangeObserved) {
console.log(row.requestedUrl, row.comparisonStatus, row.recommendedAction);
}
}
console.log(output?.value);

Python client

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("zinin/tech-stack-change-detector").call(run_input={
"items": [
"wordpress.org|WordPress,jQuery",
"shopify.com|Shopify,Cloudflare",
],
"maxConcurrency": 2,
})
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items(clean=True))
output = client.key_value_store(run["defaultKeyValueStoreId"]).get_record("OUTPUT")
review_queue = [
row for row in rows
if row.get("found") and row.get("candidateChangeObserved")
]
print(review_queue)
print(output["value"] if output else None)

Schedule a monitoring loop

The Actor does not keep baseline history. A practical recurring workflow is:

  1. run current observations for the permitted URL set;
  2. store technologies, checkedAt, entityId, and your source/catalog version in your own table;
  3. on the next schedule, render each item as URL|Technology A,Technology B;
  4. run the Actor and keep rows with candidateChangeObserved:true in a review queue;
  5. verify candidate changes with a browser, additional pages, or the account owner;
  6. update the baseline only after your verification policy passes.

Do not automatically overwrite the baseline with a partial or failed observation. Keep the evidence, comparison status, and prior baseline so a reviewer can reconstruct why a candidate event appeared.

Webhook and automation pattern

Use Apify run webhooks to notify your workflow when a run finishes. Fetch Dataset rows and OUTPUT, then branch conservatively:

  • OUTPUT.replaySafe:false → stop; inspect charges and Dataset before retrying;
  • comparisonStatus:NO_SUCCESSFUL_CURRENT_OBSERVATION → follow failure/retry policy;
  • comparisonStatus:PARTIAL_OBSERVATION_REVIEW_REQUIRED → manual/deeper check;
  • candidateChangeObserved:true → create a research task with evidence and gaps attached;
  • NO_CATALOG_CHANGE_OBSERVED → retain baseline and continue schedule;
  • NO_BASELINE_PROVIDED → optionally store the current observation as a future baseline.

Never use removed alone as a destructive automation trigger. The safer condition is still a human or a deeper verification result, because this Actor intentionally cannot prove whole-site removal.

CSV and spreadsheet use

The default Dataset can be exported to JSON, JSONL, CSV, Excel, XML, or RSS through Apify. Arrays and nested evidence serialize differently by export format. For spreadsheet review, retain at least:

  • requestedUrl, finalUrl, observedAt;
  • comparisonStatus, candidateChangeObserved;
  • candidateAddedTechnologies, candidateNotObservedTechnologies;
  • technologyEvidence, confidenceScore, confidenceReasons, dataGaps;
  • recommendedAction, actionPriority, safeToAutomate;
  • entityId, baselineId, observationId, eventId;
  • failureType, retryable, partial, error.

Flattening to only URL, added, and removed discards the information that prevents a candidate signal from becoming a false fact.

Stable IDs and joining

  • entityId — stable for the normalized requested URL;
  • baselineId — stable for the case-insensitive sorted supplied baseline set, or null when no baseline was supplied;
  • observationId — combines entity, observation time, and current catalog names;
  • eventId — combines the current observation with the supplied baseline identity.

Use entityId to join website records, baselineId to distinguish comparison inputs, and eventId to deduplicate a delivered comparison event. Because observationId includes time, a later response creates a new observation even if the technology-name set is unchanged.

Security boundaries

Buyer-supplied URLs are untrusted. Before a network connection, the Actor:

  • rejects credentials and non-HTTP(S) schemes;
  • blocks numeric private, loopback, link-local, metadata, CGNAT, multicast, unique-local, reserved, IPv4-compatible, and IPv4-mapped IPv6 destinations;
  • resolves hostnames and rejects the request if any returned address is blocked;
  • pins the verified addresses into the connection to resist DNS rebinding;
  • follows redirects manually and repeats the guard on every hop;
  • enforces timeout, redirect count, accepted content type, and byte cap;
  • treats fetched HTML as data only; it is never evaluated or executed.

These controls reduce SSRF and resource risk. They do not turn the Actor into a vulnerability scanner or authorize testing a site you do not have permission to monitor.

Privacy and retention

DataWhereTypical retention/control
Submitted URLs and baseline namesRun Input/default Key-value storeControlled by your Apify storage retention and deletion settings
Result/failure rowsDefault DatasetControlled by your Apify storage retention and deletion settings
Run summaryOUTPUT in default Key-value storeControlled by your Apify storage retention and deletion settings
Operational messagesRun logControlled by Apify log retention/settings

The Actor does not create a named cross-run history store. It does not emit raw HTML or raw response-header values. It may output the submitted URL, baseline names, current technology names, evidence types, header names, timestamps, bounded errors, and stable hashes derived from those values. Do not place secrets, session URLs, personal data, or credentials in input.

Deleting the run’s Dataset removes result rows. Deleting its default Key-value store removes Input and OUTPUT for that run. Deleting only OUTPUT does not delete Dataset rows; deleting only Dataset rows does not delete Input or OUTPUT.

Responsible use

Use URLs you are allowed to inspect and follow applicable terms, robots rules, privacy requirements, anti-spam law, and internal policy. Technology observations can support research; they do not create permission to contact a person, infer sensitive attributes, or make a high-impact decision.

The Actor is not a legal opinion. Public availability does not by itself settle every jurisdiction, contract, or intended-use question. Your use case, volume, retention, downstream enrichment, and outreach process determine the compliance work you still need to do.

What this Actor does not do

  • It does not render JavaScript or interact with consent banners.
  • It does not crawl subpages or inspect authenticated areas.
  • It does not compare against an internal stored history; you supply the baseline.
  • It does not verify when, where, or how your baseline was collected.
  • It does not prove installation, removal, license, contract, spend, usage, budget, or buying intent.
  • It does not identify company owners, employees, contacts, emails, or phone numbers.
  • It does not determine technology versions or vulnerability status.
  • It does not guarantee detection of client-rendered, obfuscated, proxied, or private technology.
  • It does not make any row safe for fully automated outreach or destructive CRM updates.

Choosing this Actor or the snapshot detector

Use Website Tech Stack Detector when you only need a current evidence-backed observation from a URL. Use Website Tech Stack Change Detector when you already have a baseline and need explicit candidate additions/non-observations plus baseline lineage. Both use bounded public-page signatures; neither proves the entire internal stack.

If you need a browser, multi-page crawl, authenticated scan, vendor verification, or persistent history, use a different tool or add those stages to your workflow.

Review checklist for a candidate change

Before changing an account field or contacting someone:

  1. Confirm found:true and partial:false.
  2. Check technologyEvidence for each candidate addition.
  3. Treat candidateNotObservedTechnologies as weak until verified elsewhere.
  4. Confirm the baseline source, timestamp, page, region, cookies, and catalog methodology.
  5. Review redirects and finalUrl; the response may belong to a different hostname.
  6. Inspect confidenceReasons and every dataGaps value.
  7. Follow recommendedAction; keep safeToAutomate:false intact.
  8. Confirm your legal basis, permission, and message relevance separately from the technical signal.
  9. Store the evidence and IDs with the decision so the event remains auditable.

FAQ

Does the Actor keep history between runs?

No. It is stateless by design. Supply a previous stack after | when you want a comparison, and store accepted baselines in your own Apify storage, database, spreadsheet, or warehouse.

Is removed proof that a technology was removed?

No. removed remains for compatibility and means the same as candidateNotObservedTechnologies: the name was in your baseline but no catalog signature was observed on the submitted page response. JavaScript rendering, subpages, CDN behavior, consent, regional variation, catalog limits, or an inaccurate baseline can all produce that difference.

Why are added and removed empty for a bare URL?

Because there is no baseline. Calling every current technology “added” would fabricate history. A bare URL returns NO_BASELINE_PROVIDED and a current observation you can choose to retain.

Does matching a Stripe signature prove the site takes payments through Stripe?

No. It proves a matching public client signature was observed in the submitted response. It does not prove production payment flow, merchant-of-record status, active contract, or transaction volume.

Can a site have zero detected technologies and still use many tools?

Yes. Client-rendered scripts, authenticated areas, subpage-only assets, proxying, obfuscation, consent behavior, and technologies outside the catalog can all hide from this bounded observation.

Does it render JavaScript?

No. It reads initial served HTML and response headers. This is faster and cheaper, but not equivalent to a browser session.

Does it respect robots.txt?

It reads the public User-agent: * block and applies longest-prefix path rules before fetching the submitted page. Missing or unreadable robots.txt is treated as no restriction. Wildcard expansion is not a promise of this simplified parser.

Are failed pages billed as results?

No result-found event is charged for a page without a successful current observation. The automatic run-start event may already have been charged.

What happens when the spending cap is reached?

The serialized billing gate stops additional paid delivery. OUTPUT shows whether work is incomplete and whether the run stopped for budget. When possible, one free advisory row explains skipped work.

Can I safely retry any failed run?

Check OUTPUT. replaySafe:true means the Actor did not observe an ambiguous linked delivery receipt. When replaySafe:false, inspect the Dataset and charged events before retrying to avoid duplicate delivery or billing.

Can I call it from an AI agent or MCP workflow?

Yes. It is a standard Apify Actor accessible through API and supported clients. Keep the review boundary: an agent can route candidate rows into research, but should not convert them into confirmed removal or outreach permission.

The Actor reads public page responses and respects its documented technical boundaries, but legality depends on jurisdiction, website terms, volume, purpose, retention, and downstream use. This README is not legal advice.

ActorWhen to use it
Website Tech Stack DetectorCurrent technology evidence without a supplied baseline
Company Profile LookupBroader company-card enrichment around a domain or company name
Company Hiring RadarPublic hiring signals and open-role research
B2B Lead EnricherMulti-source account enrichment with its own evidence boundaries
Company Registry EnricherOfficial registry identity and status fields

Quick start

  1. Click Try for free.
  2. Add one or more entries to Websites and optional previous stacks.
  3. Use a bare URL for a current observation, or URL|Technology A,Technology B for comparison.
  4. Start the run.
  5. Review Dataset rows and the OUTPUT record.
  6. Verify candidate changes before updating systems or contacting an account.

Built by zinin. Questions or a reproducible false positive? Use the Actor issue tab and include the submitted public URL, observation time, and relevant evidence type — never include tokens, credentials, or private page content.