"Which companies on my list use Google Workspace?" "Who sends mail through HubSpot?" "Is this prospect on Next.js or WordPress?"
Sales, partnerships and dev-tool marketing teams ask these questions all day. Paid enrichment databases answer them from a snapshot that may be months old. But most of the answer is public and live: in the website's HTML and headers, and in the domain's DNS.
I built two APIs that read exactly that. Here is what they returned for real companies on 1 October 2026, and how to read the result without fooling yourself.
Part 1: the website — techstack
curl --request GET \
--url 'https://tech-stack-detector2.p.rapidapi.com/api/v1/techstack?url=posthog.com' \
--header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \
--header 'X-RapidAPI-Host: tech-stack-detector2.p.rapidapi.com'
0.49 s:
{
"success": true,
"url": "https://posthog.com/",
"renderedWith": "http",
"count": 5,
"technologies": [
{ "name": "Gatsby", "category": "Static site generator", "confidence": "high", "version": "4.25.9",
"evidence": ["meta: generator: Gatsby 4.25.9", "html: <div id=\"___gatsby\">"] },
{ "name": "React", "category": "JavaScript framework", "confidence": "high", "impliedBy": "Gatsby" },
{ "name": "Tailwind CSS", "category": "UI library", "confidence": "high" },
{ "name": "Cloudinary", "category": "CDN", "confidence": "medium",
"evidence": ["media: https://res.cloudinary.com/…"] },
{ "name": "Vercel", "category": "Hosting / PaaS", "confidence": "high",
"evidence": ["header: x-vercel-id: …", "header: x-vercel-cache: HIT"] }
]
}
Every detection carries its evidence, so you can check why it was reported. For linear.app (1.41 s) it found Next.js (from an x-nextjs-cache header and _next/static scripts), React (implied), Cloudflare and Google Cloud (via: 1.1 google, medium confidence).
This is the part every "BuiltWith-style" tool does. The interesting part is the next one.
Part 2: the domain — DNS tells you the tools behind the website
A website shows you the front end. DNS shows you the company:
- MX records say who actually hosts their email (Google Workspace, Microsoft 365, their own servers…).
-
SPF
include:s list every service allowed to send mail as them — marketing automation, transactional email, support desks. - TXT verification records are left behind when someone proves domain ownership to a SaaS vendor: Slack, Notion, Atlassian, Stripe, OpenAI…
The Company Enrichment API combines this with the website, the registration record and the careers page:
curl --request GET \
--url 'https://company-enrichment-api13.p.rapidapi.com/api/v1/company/enrich?domain=linear.app' \
--header 'X-RapidAPI-Key: YOUR_RAPIDAPI_KEY' \
--header 'X-RapidAPI-Host: company-enrichment-api13.p.rapidapi.com'
2.63 s, 4 pages read (home, about, contact, careers), no browser needed. The DNS part:
"dns": {
"mxProvider": "Google Workspace",
"mxHosts": ["aspmx.l.google.com", "alt1.aspmx.l.google.com", "…"],
"dnsHost": "Cloudflare",
"spfIncludes": ["_spf.google.com", "spf.mtasv.net", "44092442.spf10.hubspotemail.net", "amazonses.com"],
"emailAuth": { "spf": true, "spfPolicy": "softfail", "dmarc": true, "dmarcPolicy": "reject" },
"services": [
{ "name": "Google Workspace", "category": "Email hosting", "evidence": "MX aspmx.l.google.com" },
{ "name": "Postmark", "category": "Email delivery", "evidence": "SPF include:spf.mtasv.net" },
{ "name": "HubSpot", "category": "Marketing automation", "evidence": "SPF include:44092442.spf10.hubspotemail.net" },
{ "name": "Amazon SES", "category": "Email delivery", "evidence": "SPF include:amazonses.com" },
{ "name": "Microsoft 365", "category": "Email hosting", "evidence": "TXT MS=…" },
{ "name": "Slack", "category": "Collaboration", "evidence": "TXT slack-domain-verification=…" },
{ "name": "Notion", "category": "Collaboration", "evidence": "TXT notion-domain-verification=…" },
{ "name": "Stripe", "category": "Payments", "evidence": "TXT stripe-verification=…" }
]
}
(8 of 19 services shown. The others included Zoom, DocuSign, LaunchDarkly, MongoDB Atlas, Rippling and a few AI vendors.) Verification values are cut at the = on purpose; the API never returns the tokens themselves.
The rest of the same response:
"name": "Linear",
"description": "Purpose-built for planning and building products with AI agents.",
"foundedYear": 2019,
"primaryEmail": "hello@linear.app",
"socialProfiles": { "x": "https://x.com/linear", "github": "https://github.com/linear", "youtube": "https://www.youtube.com/@linear" },
"domainRegistration": { "registrar": "CloudFlare, Inc.", "createdAt": "2018-05-09T23:45:22.384Z", "domainAgeYears": 8.3, "source": "rdap" },
"companySize": { "careersPage": "https://linear.app/careers", "ats": "ashby", "openJobs": 30, "hiring": true },
"evidence": { "foundedYear": { "source": "page text", "url": "https://linear.app/about" } }
So from one domain: email on Google Workspace, transactional mail through Postmark and SES, marketing mail through HubSpot, DMARC at reject, Next.js on Cloudflare, 30 open jobs on Ashby. Each field says where it came from.
How to read it without fooling yourself
This is where most "tech stack" data goes wrong, so I want to be explicit:
-
MX beats TXT for "who hosts their email". linear.app has a Microsoft
MS=verification record and Google MX records. Mail goes to Google. The Microsoft record only proves someone verified the domain with Microsoft at some point. UsemxProviderfor the email host; treat TXT-based services as "has (or had) an account". -
Self-hosted MX is not the whole story. basecamp.com runs its own MX (
mx1.basecamp.com), somxProvideris"self-hosted or other". But the API also found a Google DKIM key (google._domainkey) and Mailchimp in SPF — they still send some mail through Google and Mailchimp. - Absence is not proof. A tool that never needed DNS verification, or a site that hides its framework, will not show up.
- Headers can be rewritten. The detector only trusts Cloudflare headers when the site's DNS actually points at Cloudflare, but anything read from a page is a hint with evidence, not a fact.
Turn a domain list into a CSV
import csv, requests
HOST = "company-enrichment-api13.p.rapidapi.com"
HEADERS = {"X-RapidAPI-Key": "YOUR_RAPIDAPI_KEY", "X-RapidAPI-Host": HOST}
domains = ["linear.app", "basecamp.com", "posthog.com"]
with open("companies.csv", "w", newline="", encoding="utf-8") as f:
w = csv.writer(f)
w.writerow(["domain", "name", "email_host", "senders", "frameworks", "hosting", "ats", "open_jobs"])
for d in domains:
c = requests.get(f"https://{HOST}/api/v1/company/enrich", params={"domain": d},
headers=HEADERS, timeout=60).json()
if not c.get("success"):
w.writerow([d, c["error"]["code"]]); continue # parked_domain, target_unreachable...
dns = c.get("dns") or {}
senders = [s["name"] for s in dns.get("services", [])
if s["category"] in ("Email delivery", "Email marketing", "Marketing automation")]
cats = (c.get("techStack") or {}).get("categories", {})
size = c.get("companySize") or {}
w.writerow([d, c.get("name"), dns.get("mxProvider"), "; ".join(senders),
"; ".join(cats.get("JavaScript framework", [])), "; ".join(cats.get("Hosting / PaaS", [])),
size.get("ats"), size.get("openJobs")])
Parked or for-sale domains come back as 422 parked_domain instead of a made-up company. There is also /api/v1/company/enrich/ai, which adds an AI industry label, business model (for example nonprofit) and a one-paragraph summary; it is billed separately, so the plain endpoint never runs a model.
Price
RapidAPI plans as of 1 October 2026:
| Free | Pro | Ultra | Mega | |
|---|---|---|---|---|
| Tech Stack Detector | 100 lookups | $9.99 / 2,500 | $29.99 / 12,000 | $79.99 / 50,000 |
| Company Enrichment | 100 lookups + 25 AI | $14.99 / 5,000 + 1,500 AI | $49.99 / 25,000 + 6,000 AI | $129.99 / 100,000 + 20,000 AI |
Free plans have hard limits. Overage on paid plans is listed on each pricing page. RapidAPI charges its own bandwidth fee above 10 GB a month.
- Tech Stack Detector: https://rapidapi.com/tidytools/api/tech-stack-detector2
- Company Enrichment: https://rapidapi.com/tidytools/api/company-enrichment-api13
For lists of thousands of domains without writing code, both are also Apify Actors: https://apify.com/tidytools/website-tech-stack-detector ($4 per 1,000 sites with at least one detection) and https://apify.com/tidytools/company-website-enrichment ($6 per 1,000 companies, includes AI).
Responsible use
Everything here is public: DNS records anyone can query, and pages the company publishes. Still, use it for B2B research, not to profile individuals, and follow the anti-spam rules where you live before you email anyone you found this way.
Disclosure: I built these APIs and Actors and I earn money when people use them. Outputs are real responses from 1 October 2026; the companies were picked as well-known public examples and have no connection to me.
Top comments (1)
On "absence is not proof", the MX lookup has three different empty answers that are worth keeping apart in the response. NXDOMAIN means the domain doesn't exist, a domain with no MX record can still receive mail because senders fall back to its A or AAAA record (the implicit MX in RFC 5321), and a single MX with preference 0 and a target of "." is a Null MX (RFC 7505), the domain saying it accepts no mail. A SERVFAIL or timeout is a fourth case that says nothing either way, so returning the same null mxProvider for all four would hide a difference your users will care about.