A lead lands in your CRM with a company name and a website. Nothing else. Someone on the team now has to open the site, squint at it, guess whether it's worth a call, and hunt for a contact address — and they'll do that a few hundred times this month. The parts that are actually machine-readable (what the site runs on, which social accounts it links, which emails it publishes) are sitting in the HTML the whole time.
SiteIntel fetches a page and hands that back as JSON. One GET, one URL in, structured fields out.
The request
Auth is standard RapidAPI headers. The url param is a full https:// URL — a bare domain won't work, so normalize before you call.
curl -s 'https://siteintel.p.rapidapi.com/v1/analyze?url=https%3A%2F%2Fstripe.com' \
-H 'X-RapidAPI-Key: YOUR_KEY' \
-H 'X-RapidAPI-Host: siteintel.p.rapidapi.com'
In Node, using global fetch (18+):
const HEADERS = {
'X-RapidAPI-Key': process.env.RAPIDAPI_KEY,
'X-RapidAPI-Host': 'siteintel.p.rapidapi.com',
};
async function analyze(domain) {
const url = domain.startsWith('http') ? domain : `https://${domain}`;
const endpoint =
`https://siteintel.p.rapidapi.com/v1/analyze?url=${encodeURIComponent(url)}`;
const res = await fetch(endpoint, { headers: HEADERS });
if (!res.ok) throw new Error(`SiteIntel ${res.status}: ${await res.text()}`);
return res.json();
}
const site = await analyze('stripe.com');
console.log({
name: site.open_graph?.site_name || site.title,
tech: site.detected_tech,
socials: site.social_links,
emails: site.emails,
});
encodeURIComponent matters here. An un-encoded https:// in a query string will get chewed up by proxies and you'll spend twenty minutes debugging a 400.
What comes back
{
"query": "https://stripe.com",
"final_url": "https://stripe.com/",
"status": 200,
"fetched_at": "2026-08-21T14:02:11Z",
"title": "Stripe | Financial Infrastructure to Grow Your Revenue",
"description": "Stripe powers online and in-person payment processing...",
"canonical": "https://stripe.com/",
"lang": "en",
"favicon": "https://stripe.com/favicon.ico",
"open_graph": {
"title": "Stripe | Financial Infrastructure",
"image": "https://images.stripeassets.com/og.png",
"site_name": "Stripe",
"type": "website"
},
"detected_tech": ["Cloudflare", "React"],
"social_links": ["https://twitter.com/stripe", "https://www.linkedin.com/company/stripe"],
"emails": ["press@stripe.com"],
"server": "nginx"
}
Three things about this shape that will save you a rewrite:
detected_tech and social_links are flat arrays of strings. Not objects with name/confidence, not a keyed map. detected_tech.includes('Shopify') is the whole check. Same for social_links — plain URL strings, so you route them yourself:
const platformOf = (u) => {
const host = new URL(u).hostname.replace(/^www\./, '');
return host.split('.')[0]; // twitter, linkedin, facebook, github...
};
const byPlatform = Object.fromEntries(
site.social_links.map((u) => [platformOf(u), u])
);
final_url is not query. It's where the fetch actually ended up after redirects. Compare the two and you get domain migrations and www/apex canonicalization for free — useful when your CRM has three records for the same company under two spellings. There's no top-level domain field; parse it off final_url if you need one.
status is the upstream site's HTTP status, not the API's. A 200 from SiteIntel with "status": 404 in the body means the request worked and the lead's page is broken. Treat those separately — an empty detected_tech on a 404 tells you nothing, but an empty one on a 200 is real signal.
emails only contains addresses the site publishes in its own HTML — hello@, sales@, press@. It is not a contact database, and it's frequently an empty array. Treat a hit as a bonus routing hint, not as your outbound list, and check the sending rules for whatever jurisdiction you're in before you mail anything you found this way.
One thing worth building with it
The obvious use is a CRM enrichment worker: pull leads with a website and no enrichment timestamp, call /v1/analyze, write the fields back.
for (const lead of await db.leadsNeedingEnrichment(200)) {
try {
const site = await analyze(lead.website);
await db.updateLead(lead.id, {
company_name: site.open_graph?.site_name ?? site.title ?? lead.company_name,
canonical_url: site.final_url,
tech_stack: site.detected_tech, // store as-is, it's already strings
linkedin: site.social_links.find((u) => u.includes('linkedin.com')) ?? null,
public_email: site.emails[0] ?? null,
enriched_at: site.fetched_at,
});
} catch (err) {
await db.markEnrichmentFailed(lead.id, String(err));
}
await new Promise((r) => setTimeout(r, 250)); // be polite, stay under your plan's rate
}
The payoff isn't the fields themselves, it's the routing you can do once detected_tech is a queryable column. If you sell a Shopify app, detected_tech @> '["Shopify"]' is your entire lead-scoring rule. If you sell an alternative to something, matching on the incumbent's name gives you a segment that's worth a different email than everyone else gets. Cloudflare and nginx tell you close to nothing about buying intent, so don't build a score that weights them.
Two adjacent endpoints, same auth and same url param, if you want more than the JSON:
# on-page SEO report — useful if your pitch is "your site has problems"
curl -s 'https://siteintel.p.rapidapi.com/v1/seo-audit?url=https%3A%2F%2Fexample.com' \
-H 'X-RapidAPI-Key: YOUR_KEY' -H 'X-RapidAPI-Host: siteintel.p.rapidapi.com'
# screenshot — drop it into the CRM record so reps see the site without opening it
curl -s 'https://siteintel.p.rapidapi.com/v1/screenshot?url=https%3A%2F%2Fexample.com' \
-H 'X-RapidAPI-Key: YOUR_KEY' -H 'X-RapidAPI-Host: siteintel.p.rapidapi.com'
Cache aggressively. A company's stack doesn't change between Tuesday and Wednesday, so key on final_url and re-enrich monthly rather than on every CRM read.
Runnable examples are in the repo: github.com/clause-netizen/siteintel-api — it's also on RapidAPI if you'd rather not manage a key.
Top comments (1)
This is a really practical approach to CRM enrichment—especially the idea of letting the domain do most of the heavy lifting instead of asking sales teams to manually fill in all that context. ~