
Similarweb Traffic
- 104 installs
- 614 repo stars
- Updated August 5, 2026
- openclaudia/openclaudia-skills
Helps with ai & agent building tasks.
About
similarweb-traffic is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- similarweb-traffic
- AI & Agent Building
- AI-coding skill
Similarweb Traffic by the numbers
- 104 all-time installs (skills.sh)
- +16 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #4,243 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openclaudia/openclaudia-skills --skill similarweb-trafficAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 104 |
|---|---|
| repo stars | ★ 614 |
| Last updated | August 5, 2026 |
| Repository | openclaudia/openclaudia-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
SimilarWeb Traffic
Fetch traffic-overview estimates for any domain — monthly visits, traffic-source breakdown, top countries, top keywords, engagement metrics, and global/category ranks.
When to Use This Skill
Use this skill when the user:
- Asks how much traffic a domain gets ("what's the traffic for X")
- Wants monthly visit trends for a site
- Needs a traffic-source breakdown (organic vs direct vs paid vs GenAI, etc.)
- Wants to know a site's audience countries
- Wants to compare/benchmark several domains against each other or competitors
Usage
No API key required. Self-contained Node script (uses built-in fetch, no npm install).
node scripts/fetch-similarweb-traffic.js <domain>Examples:
# Formatted markdown summary (default)
node scripts/fetch-similarweb-traffic.js chatslide.ai
# Raw normalized JSON (for further processing / comparisons)
node scripts/fetch-similarweb-traffic.js stripe.com --jsonThe domain can be passed bare (example.com) or as a URL (https://www.example.com/pricing) — it gets normalized (strips scheme, www., and path).
Comparing multiple domains
Run the script once per domain with --json and combine the results into a table. Add a short delay between calls to be polite to the endpoint.
Output
Default mode prints a markdown summary: a metrics table (monthly visits, ranks, bounce, pages/visit, time on site), a monthly-visits trend line, traffic-source breakdown, top countries, and top keywords.
--json mode prints a normalized object:
{
"domain": "stripe.com",
"globalRank": 327,
"categoryRank": 21,
"category": "Finance",
"snapshotMonth": "2026-05",
"visits": 122394040,
"bounceRate": 0.55,
"pagesPerVisit": 3.26,
"timeOnSite": 165.9,
"estimatedMonthlyVisits": [{ "date": "2026-03-01", "visits": 118067753 }],
"trafficSources": { "searchOrganic": 0.48, "direct": 0.23, "...": 0 },
"topCountries": [{ "countryCode": "US", "value": 0.087 }],
"topKeywords": ["..."]
}visits is the most recent complete month. Rates (bounceRate, all trafficSources, topCountries[].value) are 0–1 fractions.
API Details
- Endpoint:
https://data.similarweb.com/api/v1/data?domain=<domain>— SimilarWeb's undocumented public data endpoint (the one their browser extension uses). No auth, no key, free. - Requires a browser
user-agentheader (the script sends one) — defaultcurl/bot agents are blocked. - Synchronous, returns immediately.
Important Notes
- Estimates, not ground truth. SimilarWeb models traffic; numbers can be off, especially for mid/low-traffic sites. For a domain you own, your own Google Analytics / Search Console data is authoritative — prefer those when available.
- Unknown domains return zeros, not an error. SimilarWeb responds HTTP 200 with all-zero/synthetic data when it has no data for a domain. If every visit count is 0, treat it as "no SimilarWeb data."
- Unofficial endpoint. It can be rate-limited, change shape, or require auth without notice; this is a gray-area use of SimilarWeb's data outside their paid API.
- Exit codes:
2network error,3non-200 HTTP response.
#!/usr/bin/env node
// Fetch traffic overview for a domain from SimilarWeb's public data endpoint.
// No dependencies, no API key. Same endpoint PageGun's fetchSimilarwebTraffic() uses.
//
// Usage:
// node fetch-similarweb-traffic.js <domain> [--json]
// node fetch-similarweb-traffic.js chatslide.ai
// node fetch-similarweb-traffic.js chatslide.ai --json # raw normalized JSON only
const DATA_URL = 'https://data.similarweb.com/api/v1/data'
const USER_AGENT =
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
function normalizeDomain(domain) {
return String(domain || '')
.trim()
.replace(/^https?:\/\//i, '')
.replace(/^www\./i, '')
.replace(/\/.*$/, '')
.toLowerCase()
}
function num(v) {
if (v === null || v === undefined || v === '') return null
const n = Number(v)
return Number.isFinite(n) ? n : null
}
function fmt(n) {
return n === null ? 'n/a' : n.toLocaleString('en-US')
}
function pct(v) {
const n = num(v)
return n === null ? 'n/a' : `${(n * 100).toFixed(1)}%`
}
async function main() {
const args = process.argv.slice(2)
const jsonOnly = args.includes('--json')
const domain = normalizeDomain(args.find((a) => !a.startsWith('--')))
if (!domain) {
console.error('Usage: node fetch-similarweb-traffic.js <domain> [--json]')
process.exit(1)
}
const url = new URL(DATA_URL)
url.searchParams.set('domain', domain)
let res
try {
res = await fetch(url, {
headers: {
accept: 'application/json,text/plain,*/*',
'accept-language': 'en-US,en;q=0.9',
'user-agent': USER_AGENT,
},
})
} catch (err) {
console.error(`Network error fetching ${domain}: ${err.message}`)
process.exit(2)
}
if (!res.ok) {
console.error(`SimilarWeb request failed for ${domain}: HTTP ${res.status}`)
if (res.status === 404) console.error('(404 usually means SimilarWeb has no data for this domain.)')
process.exit(3)
}
const d = await res.json()
const e = d.Engagments || {}
const ts = d.TrafficSources || {}
const overview = {
domain,
siteName: d.SiteName ?? null,
title: d.Title ?? null,
description: d.Description ?? null,
globalRank: num(d.GlobalRank && d.GlobalRank.Rank),
categoryRank: num(d.CategoryRank && d.CategoryRank.Rank),
category: (d.CategoryRank && d.CategoryRank.Category) ?? null,
snapshotMonth:
e.Year && e.Month ? `${e.Year}-${String(e.Month).padStart(2, '0')}` : null,
visits: num(e.Visits),
bounceRate: num(e.BounceRate),
pagesPerVisit: num(e.PagePerVisit),
timeOnSite: num(e.TimeOnSite),
estimatedMonthlyVisits: Object.entries(d.EstimatedMonthlyVisits || {})
.map(([date, v]) => ({ date, visits: num(v) }))
.sort((a, b) => a.date.localeCompare(b.date)),
trafficSources: {
searchOrganic: num(ts.SearchOrganic),
searchPaid: num(ts.SearchPaid),
direct: num(ts.Direct),
referrals: num(ts.Referrals),
socialOrganic: num(ts.SocialOrganic),
socialPaid: num(ts.SocialPaid),
mail: num(ts.Mail),
displayAds: num(ts['Display Ads'] ?? ts.DisplayAds),
genAi: num(ts.GenAi),
affiliate: num(ts.Affiliate),
},
topCountries: (d.TopCountryShares || []).map((c) => ({
countryCode: c.CountryCode ?? null,
value: num(c.Value),
})),
topKeywords: (d.TopKeywords || []).map((k) => k.Name).filter(Boolean),
}
if (jsonOnly) {
console.log(JSON.stringify(overview, null, 2))
return
}
const lines = []
lines.push(`# SimilarWeb traffic — ${domain}`)
if (overview.snapshotMonth) lines.push(`Snapshot month: ${overview.snapshotMonth}`)
lines.push('')
lines.push('| Metric | Value |')
lines.push('|---|---|')
lines.push(`| Monthly visits | ${fmt(overview.visits)} |`)
lines.push(`| Global rank | ${overview.globalRank ? '#' + fmt(overview.globalRank) : 'n/a'} |`)
lines.push(`| Category rank | ${overview.categoryRank ? '#' + fmt(overview.categoryRank) : 'n/a'} |`)
lines.push(`| Category | ${overview.category ?? 'n/a'} |`)
lines.push(`| Bounce rate | ${pct(overview.bounceRate)} |`)
lines.push(`| Pages / visit | ${overview.pagesPerVisit === null ? 'n/a' : overview.pagesPerVisit.toFixed(2)} |`)
lines.push(`| Avg. time on site | ${overview.timeOnSite === null ? 'n/a' : Math.round(overview.timeOnSite) + 's'} |`)
if (overview.estimatedMonthlyVisits.length) {
lines.push('')
lines.push('**Monthly visits trend:**')
lines.push(
overview.estimatedMonthlyVisits
.map((p) => `${p.date.slice(0, 7)}: ${fmt(p.visits)}`)
.join(' → '),
)
}
const srcEntries = Object.entries(overview.trafficSources)
.filter(([, v]) => v !== null && v > 0)
.sort((a, b) => b[1] - a[1])
if (srcEntries.length) {
lines.push('')
lines.push('**Traffic sources:**')
for (const [k, v] of srcEntries) lines.push(`- ${k}: ${pct(v)}`)
}
if (overview.topCountries.length) {
lines.push('')
lines.push('**Top countries:** ' + overview.topCountries
.map((c) => `${c.countryCode} ${pct(c.value)}`)
.join(', '))
}
if (overview.topKeywords.length) {
lines.push('')
lines.push('**Top keywords:** ' + overview.topKeywords.slice(0, 8).join(', '))
}
console.log(lines.join('\n'))
}
main().catch((err) => {
console.error(err.message)
process.exit(1)
})