Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
474 changes: 218 additions & 256 deletions apps/sim/blocks/blocks/ahrefs.ts

Large diffs are not rendered by default.

32 changes: 14 additions & 18 deletions apps/sim/tools/ahrefs/backlinks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { AhrefsBacklinksParams, AhrefsBacklinksResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'

const SELECT_FIELDS =
'url_from,url_to,anchor,domain_rating_source,is_dofollow,first_seen,last_visited'

export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksResponse> = {
id: 'ahrefs_backlinks',
name: 'Ahrefs Backlinks',
Expand All @@ -21,25 +24,20 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
history: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return. Example: 50 (default: 100)',
description:
'Historical scope: "live" (currently live backlinks), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date).',
},
offset: {
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
Expand All @@ -51,14 +49,12 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes

request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks')
const url = new URL('https://api.ahrefs.com/v3/site-explorer/all-backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
url.searchParams.set('select', SELECT_FIELDS)
if (params.mode) url.searchParams.set('mode', params.mode)
url.searchParams.set('history', params.history || 'all_time')
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
Expand All @@ -79,8 +75,8 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
isDofollow: link.is_dofollow ?? link.dofollow ?? false,
domainRatingSource: link.domain_rating_source ?? 0,
isDofollow: link.is_dofollow ?? false,
firstSeen: link.first_seen || '',
lastVisited: link.last_visited || '',
}))
Expand Down
39 changes: 23 additions & 16 deletions apps/sim/tools/ahrefs/backlinks_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const backlinksStatsTool: ToolConfig<
id: 'ahrefs_backlinks_stats',
name: 'Ahrefs Backlinks Stats',
description:
'Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.',
'Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.',
version: '1.0.0',

params: {
Expand All @@ -24,13 +24,13 @@ export const backlinksStatsTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
},
apiKey: {
type: 'string',
Expand Down Expand Up @@ -64,16 +64,16 @@ export const backlinksStatsTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
}

const metrics = data.metrics || {}

return {
success: true,
output: {
stats: {
total: data.live ?? data.total ?? 0,
dofollow: data.live_dofollow ?? data.dofollow ?? 0,
nofollow: data.live_nofollow ?? data.nofollow ?? 0,
text: data.text ?? 0,
image: data.image ?? 0,
redirect: data.redirect ?? 0,
liveBacklinks: metrics.live ?? 0,
liveReferringDomains: metrics.live_refdomains ?? 0,
allTimeBacklinks: metrics.all_time ?? 0,
allTimeReferringDomains: metrics.all_time_refdomains ?? 0,
},
},
}
Expand All @@ -82,14 +82,21 @@ export const backlinksStatsTool: ToolConfig<
outputs: {
stats: {
type: 'object',
description: 'Backlink statistics summary',
description: 'Backlink and referring domain totals',
properties: {
total: { type: 'number', description: 'Total number of live backlinks' },
dofollow: { type: 'number', description: 'Number of dofollow backlinks' },
nofollow: { type: 'number', description: 'Number of nofollow backlinks' },
text: { type: 'number', description: 'Number of text backlinks' },
image: { type: 'number', description: 'Number of image backlinks' },
redirect: { type: 'number', description: 'Number of redirect backlinks' },
liveBacklinks: { type: 'number', description: 'Number of currently live backlinks' },
liveReferringDomains: {
type: 'number',
description: 'Number of currently live referring domains',
},
allTimeBacklinks: {
type: 'number',
description: 'Total backlinks ever discovered, including lost ones',
},
allTimeReferringDomains: {
type: 'number',
description: 'Total referring domains ever discovered, including lost ones',
},
},
},
},
Expand Down
35 changes: 13 additions & 22 deletions apps/sim/tools/ahrefs/broken_backlinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import type {
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'

const SELECT_FIELDS = 'url_from,url_to,http_code_target,anchor,domain_rating_source'

export const brokenBacklinksTool: ToolConfig<
AhrefsBrokenBacklinksParams,
AhrefsBrokenBacklinksResponse
Expand All @@ -27,25 +29,13 @@ export const brokenBacklinksTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return. Example: 50 (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
Expand All @@ -59,12 +49,9 @@ export const brokenBacklinksTool: ToolConfig<
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
url.searchParams.set('select', SELECT_FIELDS)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
Expand All @@ -81,12 +68,12 @@ export const brokenBacklinksTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
}

const brokenBacklinks = (data.backlinks || data.broken_backlinks || []).map((link: any) => ({
const brokenBacklinks = (data.backlinks || []).map((link: any) => ({
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
httpCode: link.http_code ?? link.status_code ?? 404,
httpCode: link.http_code_target ?? null,
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
domainRatingSource: link.domain_rating_source ?? 0,
}))

return {
Expand All @@ -109,7 +96,11 @@ export const brokenBacklinksTool: ToolConfig<
description: 'The URL of the page containing the broken link',
},
urlTo: { type: 'string', description: 'The broken URL being linked to' },
httpCode: { type: 'number', description: 'HTTP status code (e.g., 404, 410)' },
httpCode: {
type: 'number',
description: 'HTTP status code of the broken target URL (e.g., 404, 410)',
optional: true,
},
anchor: { type: 'string', description: 'The anchor text of the link' },
domainRatingSource: {
type: 'number',
Expand Down
5 changes: 3 additions & 2 deletions apps/sim/tools/ahrefs/domain_rating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
return {
success: true,
output: {
domainRating: data.domain_rating ?? 0,
ahrefsRank: data.ahrefs_rank ?? 0,
domainRating: data.domain_rating?.domain_rating ?? 0,
ahrefsRank: data.domain_rating?.ahrefs_rank ?? null,
},
}
},
Expand All @@ -69,6 +69,7 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
ahrefsRank: {
type: 'number',
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
optional: true,
},
},
}
6 changes: 6 additions & 0 deletions apps/sim/tools/ahrefs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
import { metricsTool } from '@/tools/ahrefs/metrics'
import { organicCompetitorsTool } from '@/tools/ahrefs/organic_competitors'
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
import { topPagesTool } from '@/tools/ahrefs/top_pages'
Expand All @@ -15,3 +17,7 @@ export const ahrefsOrganicKeywordsTool = organicKeywordsTool
export const ahrefsTopPagesTool = topPagesTool
export const ahrefsKeywordOverviewTool = keywordOverviewTool
export const ahrefsBrokenBacklinksTool = brokenBacklinksTool
export const ahrefsMetricsTool = metricsTool
export const ahrefsOrganicCompetitorsTool = organicCompetitorsTool

export * from '@/tools/ahrefs/types'
54 changes: 41 additions & 13 deletions apps/sim/tools/ahrefs/keyword_overview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type {
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'

const SELECT_FIELDS =
'keyword,volume,difficulty,cpc,clicks,searches_pct_clicks_organic_only,parent_topic,traffic_potential,intents'

export const keywordOverviewTool: ToolConfig<
AhrefsKeywordOverviewParams,
AhrefsKeywordOverviewResponse
Expand Down Expand Up @@ -38,8 +41,9 @@ export const keywordOverviewTool: ToolConfig<
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
url.searchParams.set('keyword', params.keyword)
url.searchParams.set('keywords', params.keyword)
url.searchParams.set('country', params.country || 'us')
url.searchParams.set('select', SELECT_FIELDS)
return url.toString()
},
method: 'GET',
Expand All @@ -56,18 +60,21 @@ export const keywordOverviewTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
}

const result = (data.keywords || [])[0] || {}

return {
success: true,
output: {
overview: {
keyword: data.keyword || '',
searchVolume: data.volume ?? 0,
keywordDifficulty: data.keyword_difficulty ?? data.difficulty ?? 0,
cpc: data.cpc ?? 0,
clicks: data.clicks ?? 0,
clicksPercentage: data.clicks_percentage ?? 0,
parentTopic: data.parent_topic || '',
trafficPotential: data.traffic_potential ?? 0,
keyword: result.keyword || '',
searchVolume: result.volume ?? 0,
keywordDifficulty: result.difficulty ?? null,
cpc: result.cpc ?? null,
clicks: result.clicks ?? null,
clicksPercentage: result.searches_pct_clicks_organic_only ?? null,
parentTopic: result.parent_topic ?? null,
trafficPotential: result.traffic_potential ?? null,
intents: result.intents ?? null,
},
},
}
Expand All @@ -83,17 +90,38 @@ export const keywordOverviewTool: ToolConfig<
keywordDifficulty: {
type: 'number',
description: 'Keyword difficulty score (0-100)',
optional: true,
},
cpc: { type: 'number', description: 'Cost per click in USD' },
clicks: { type: 'number', description: 'Estimated clicks per month' },
cpc: { type: 'number', description: 'Cost per click in USD', optional: true },
clicks: { type: 'number', description: 'Estimated clicks per month', optional: true },
clicksPercentage: {
type: 'number',
description: 'Percentage of searches that result in clicks',
description: 'Percentage of searches that result in an organic click',
optional: true,
},
parentTopic: {
type: 'string',
description: 'The parent topic for this keyword',
optional: true,
},
parentTopic: { type: 'string', description: 'The parent topic for this keyword' },
trafficPotential: {
type: 'number',
description: 'Estimated traffic potential if ranking #1',
optional: true,
},
intents: {
type: 'object',
description:
'Search intent flags (informational, navigational, commercial, transactional, branded, local)',
optional: true,
properties: {
informational: { type: 'boolean', description: 'Query seeks information' },
navigational: { type: 'boolean', description: 'Query seeks a specific site or page' },
commercial: { type: 'boolean', description: 'Query researches a purchase decision' },
transactional: { type: 'boolean', description: 'Query intends to complete a purchase' },
branded: { type: 'boolean', description: 'Query references a specific brand' },
local: { type: 'boolean', description: 'Query seeks local results' },
},
},
},
},
Expand Down
Loading
Loading