Skip to content

Commit 363a0cf

Browse files
committed
fix(ahrefs): align tool coverage and outputs with the Ahrefs API v3
- 5 of 8 tools were missing the required `select` param (API v3 rejects list-endpoint requests without it) and 3 sent a `date` param the endpoint doesn't accept - all list endpoints sent an `offset` param that doesn't exist on any Ahrefs v3 site-explorer endpoint (only `limit` is supported) - domain_rating and backlinks_stats read response fields at the wrong nesting level and always returned zeros; several other tools mapped output fields to column names the API doesn't return (position, url, traffic, backlinks, dofollow_backlinks, http_code) instead of the real ones (best_position, best_position_url, sum_traffic, links_to_target, dofollow_links, http_code_target) - keyword_overview used the wrong endpoint param entirely (`keyword` instead of `keywords`) so it always returned empty - added ahrefs_metrics (site-explorer/metrics) and ahrefs_organic_competitors (site-explorer/organic-competitors) tools - fixed docsLink to point at docs.sim.ai instead of ahrefs.com
1 parent 220da44 commit 363a0cf

14 files changed

Lines changed: 711 additions & 437 deletions

apps/sim/blocks/blocks/ahrefs.ts

Lines changed: 213 additions & 255 deletions
Large diffs are not rendered by default.

apps/sim/tools/ahrefs/backlinks.ts

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import type { AhrefsBacklinksParams, AhrefsBacklinksResponse } from '@/tools/ahrefs/types'
22
import type { ToolConfig } from '@/tools/types'
33

4+
const SELECT_FIELDS =
5+
'url_from,url_to,anchor,domain_rating_source,is_dofollow,first_seen,last_visited'
6+
47
export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksResponse> = {
58
id: 'ahrefs_backlinks',
69
name: 'Ahrefs Backlinks',
@@ -21,25 +24,20 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
2124
required: false,
2225
visibility: 'user-or-llm',
2326
description:
24-
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
27+
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
2528
},
26-
date: {
29+
history: {
2730
type: 'string',
2831
required: false,
29-
visibility: 'user-only',
30-
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
31-
},
32-
limit: {
33-
type: 'number',
34-
required: false,
3532
visibility: 'user-or-llm',
36-
description: 'Maximum number of results to return. Example: 50 (default: 100)',
33+
description:
34+
'Historical scope: "live" (currently live backlinks), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date).',
3735
},
38-
offset: {
36+
limit: {
3937
type: 'number',
4038
required: false,
4139
visibility: 'user-or-llm',
42-
description: 'Number of results to skip for pagination. Example: 100',
40+
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
4341
},
4442
apiKey: {
4543
type: 'string',
@@ -51,14 +49,12 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
5149

5250
request: {
5351
url: (params) => {
54-
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks')
52+
const url = new URL('https://api.ahrefs.com/v3/site-explorer/all-backlinks')
5553
url.searchParams.set('target', params.target)
56-
// Date is required - default to today if not provided
57-
const date = params.date || new Date().toISOString().split('T')[0]
58-
url.searchParams.set('date', date)
54+
url.searchParams.set('select', SELECT_FIELDS)
5955
if (params.mode) url.searchParams.set('mode', params.mode)
56+
if (params.history) url.searchParams.set('history', params.history)
6057
if (params.limit) url.searchParams.set('limit', String(params.limit))
61-
if (params.offset) url.searchParams.set('offset', String(params.offset))
6258
return url.toString()
6359
},
6460
method: 'GET',
@@ -79,8 +75,8 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
7975
urlFrom: link.url_from || '',
8076
urlTo: link.url_to || '',
8177
anchor: link.anchor || '',
82-
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
83-
isDofollow: link.is_dofollow ?? link.dofollow ?? false,
78+
domainRatingSource: link.domain_rating_source ?? 0,
79+
isDofollow: link.is_dofollow ?? false,
8480
firstSeen: link.first_seen || '',
8581
lastVisited: link.last_visited || '',
8682
}))

apps/sim/tools/ahrefs/backlinks_stats.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const backlinksStatsTool: ToolConfig<
88
id: 'ahrefs_backlinks_stats',
99
name: 'Ahrefs Backlinks Stats',
1010
description:
11-
'Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.',
11+
'Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.',
1212
version: '1.0.0',
1313

1414
params: {
@@ -24,13 +24,13 @@ export const backlinksStatsTool: ToolConfig<
2424
required: false,
2525
visibility: 'user-or-llm',
2626
description:
27-
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
27+
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
2828
},
2929
date: {
3030
type: 'string',
3131
required: false,
3232
visibility: 'user-only',
33-
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
33+
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
3434
},
3535
apiKey: {
3636
type: 'string',
@@ -64,16 +64,16 @@ export const backlinksStatsTool: ToolConfig<
6464
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
6565
}
6666

67+
const metrics = data.metrics || {}
68+
6769
return {
6870
success: true,
6971
output: {
7072
stats: {
71-
total: data.live ?? data.total ?? 0,
72-
dofollow: data.live_dofollow ?? data.dofollow ?? 0,
73-
nofollow: data.live_nofollow ?? data.nofollow ?? 0,
74-
text: data.text ?? 0,
75-
image: data.image ?? 0,
76-
redirect: data.redirect ?? 0,
73+
liveBacklinks: metrics.live ?? 0,
74+
liveReferringDomains: metrics.live_refdomains ?? 0,
75+
allTimeBacklinks: metrics.all_time ?? 0,
76+
allTimeReferringDomains: metrics.all_time_refdomains ?? 0,
7777
},
7878
},
7979
}
@@ -82,14 +82,21 @@ export const backlinksStatsTool: ToolConfig<
8282
outputs: {
8383
stats: {
8484
type: 'object',
85-
description: 'Backlink statistics summary',
85+
description: 'Backlink and referring domain totals',
8686
properties: {
87-
total: { type: 'number', description: 'Total number of live backlinks' },
88-
dofollow: { type: 'number', description: 'Number of dofollow backlinks' },
89-
nofollow: { type: 'number', description: 'Number of nofollow backlinks' },
90-
text: { type: 'number', description: 'Number of text backlinks' },
91-
image: { type: 'number', description: 'Number of image backlinks' },
92-
redirect: { type: 'number', description: 'Number of redirect backlinks' },
87+
liveBacklinks: { type: 'number', description: 'Number of currently live backlinks' },
88+
liveReferringDomains: {
89+
type: 'number',
90+
description: 'Number of currently live referring domains',
91+
},
92+
allTimeBacklinks: {
93+
type: 'number',
94+
description: 'Total backlinks ever discovered, including lost ones',
95+
},
96+
allTimeReferringDomains: {
97+
type: 'number',
98+
description: 'Total referring domains ever discovered, including lost ones',
99+
},
93100
},
94101
},
95102
},

apps/sim/tools/ahrefs/broken_backlinks.ts

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type {
44
} from '@/tools/ahrefs/types'
55
import type { ToolConfig } from '@/tools/types'
66

7+
const SELECT_FIELDS = 'url_from,url_to,http_code_target,anchor,domain_rating_source'
8+
79
export const brokenBacklinksTool: ToolConfig<
810
AhrefsBrokenBacklinksParams,
911
AhrefsBrokenBacklinksResponse
@@ -27,25 +29,13 @@ export const brokenBacklinksTool: ToolConfig<
2729
required: false,
2830
visibility: 'user-or-llm',
2931
description:
30-
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
31-
},
32-
date: {
33-
type: 'string',
34-
required: false,
35-
visibility: 'user-only',
36-
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
32+
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
3733
},
3834
limit: {
3935
type: 'number',
4036
required: false,
4137
visibility: 'user-or-llm',
42-
description: 'Maximum number of results to return. Example: 50 (default: 100)',
43-
},
44-
offset: {
45-
type: 'number',
46-
required: false,
47-
visibility: 'user-or-llm',
48-
description: 'Number of results to skip for pagination. Example: 100',
38+
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
4939
},
5040
apiKey: {
5141
type: 'string',
@@ -59,12 +49,9 @@ export const brokenBacklinksTool: ToolConfig<
5949
url: (params) => {
6050
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
6151
url.searchParams.set('target', params.target)
62-
// Date is required - default to today if not provided
63-
const date = params.date || new Date().toISOString().split('T')[0]
64-
url.searchParams.set('date', date)
52+
url.searchParams.set('select', SELECT_FIELDS)
6553
if (params.mode) url.searchParams.set('mode', params.mode)
6654
if (params.limit) url.searchParams.set('limit', String(params.limit))
67-
if (params.offset) url.searchParams.set('offset', String(params.offset))
6855
return url.toString()
6956
},
7057
method: 'GET',
@@ -81,12 +68,12 @@ export const brokenBacklinksTool: ToolConfig<
8168
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
8269
}
8370

84-
const brokenBacklinks = (data.backlinks || data.broken_backlinks || []).map((link: any) => ({
71+
const brokenBacklinks = (data.backlinks || []).map((link: any) => ({
8572
urlFrom: link.url_from || '',
8673
urlTo: link.url_to || '',
87-
httpCode: link.http_code ?? link.status_code ?? 404,
74+
httpCode: link.http_code_target ?? null,
8875
anchor: link.anchor || '',
89-
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
76+
domainRatingSource: link.domain_rating_source ?? 0,
9077
}))
9178

9279
return {
@@ -109,7 +96,11 @@ export const brokenBacklinksTool: ToolConfig<
10996
description: 'The URL of the page containing the broken link',
11097
},
11198
urlTo: { type: 'string', description: 'The broken URL being linked to' },
112-
httpCode: { type: 'number', description: 'HTTP status code (e.g., 404, 410)' },
99+
httpCode: {
100+
type: 'number',
101+
description: 'HTTP status code of the broken target URL (e.g., 404, 410)',
102+
optional: true,
103+
},
113104
anchor: { type: 'string', description: 'The anchor text of the link' },
114105
domainRatingSource: {
115106
type: 'number',

apps/sim/tools/ahrefs/domain_rating.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
5555
return {
5656
success: true,
5757
output: {
58-
domainRating: data.domain_rating ?? 0,
59-
ahrefsRank: data.ahrefs_rank ?? 0,
58+
domainRating: data.domain_rating?.domain_rating ?? 0,
59+
ahrefsRank: data.domain_rating?.ahrefs_rank ?? null,
6060
},
6161
}
6262
},
@@ -69,6 +69,7 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
6969
ahrefsRank: {
7070
type: 'number',
7171
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
72+
optional: true,
7273
},
7374
},
7475
}

apps/sim/tools/ahrefs/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
33
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
44
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
55
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
6+
import { metricsTool } from '@/tools/ahrefs/metrics'
7+
import { organicCompetitorsTool } from '@/tools/ahrefs/organic_competitors'
68
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
79
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
810
import { topPagesTool } from '@/tools/ahrefs/top_pages'
@@ -15,3 +17,7 @@ export const ahrefsOrganicKeywordsTool = organicKeywordsTool
1517
export const ahrefsTopPagesTool = topPagesTool
1618
export const ahrefsKeywordOverviewTool = keywordOverviewTool
1719
export const ahrefsBrokenBacklinksTool = brokenBacklinksTool
20+
export const ahrefsMetricsTool = metricsTool
21+
export const ahrefsOrganicCompetitorsTool = organicCompetitorsTool
22+
23+
export * from '@/tools/ahrefs/types'

apps/sim/tools/ahrefs/keyword_overview.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import type {
44
} from '@/tools/ahrefs/types'
55
import type { ToolConfig } from '@/tools/types'
66

7+
const SELECT_FIELDS =
8+
'keyword,volume,difficulty,cpc,clicks,searches_pct_clicks_organic_only,parent_topic,traffic_potential'
9+
710
export const keywordOverviewTool: ToolConfig<
811
AhrefsKeywordOverviewParams,
912
AhrefsKeywordOverviewResponse
@@ -38,8 +41,9 @@ export const keywordOverviewTool: ToolConfig<
3841
request: {
3942
url: (params) => {
4043
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
41-
url.searchParams.set('keyword', params.keyword)
44+
url.searchParams.set('keywords', params.keyword)
4245
url.searchParams.set('country', params.country || 'us')
46+
url.searchParams.set('select', SELECT_FIELDS)
4347
return url.toString()
4448
},
4549
method: 'GET',
@@ -56,18 +60,20 @@ export const keywordOverviewTool: ToolConfig<
5660
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
5761
}
5862

63+
const result = (data.keywords || [])[0] || {}
64+
5965
return {
6066
success: true,
6167
output: {
6268
overview: {
63-
keyword: data.keyword || '',
64-
searchVolume: data.volume ?? 0,
65-
keywordDifficulty: data.keyword_difficulty ?? data.difficulty ?? 0,
66-
cpc: data.cpc ?? 0,
67-
clicks: data.clicks ?? 0,
68-
clicksPercentage: data.clicks_percentage ?? 0,
69-
parentTopic: data.parent_topic || '',
70-
trafficPotential: data.traffic_potential ?? 0,
69+
keyword: result.keyword || '',
70+
searchVolume: result.volume ?? 0,
71+
keywordDifficulty: result.difficulty ?? null,
72+
cpc: result.cpc ?? null,
73+
clicks: result.clicks ?? null,
74+
clicksPercentage: result.searches_pct_clicks_organic_only ?? null,
75+
parentTopic: result.parent_topic ?? null,
76+
trafficPotential: result.traffic_potential ?? null,
7177
},
7278
},
7379
}
@@ -83,17 +89,24 @@ export const keywordOverviewTool: ToolConfig<
8389
keywordDifficulty: {
8490
type: 'number',
8591
description: 'Keyword difficulty score (0-100)',
92+
optional: true,
8693
},
87-
cpc: { type: 'number', description: 'Cost per click in USD' },
88-
clicks: { type: 'number', description: 'Estimated clicks per month' },
94+
cpc: { type: 'number', description: 'Cost per click in USD', optional: true },
95+
clicks: { type: 'number', description: 'Estimated clicks per month', optional: true },
8996
clicksPercentage: {
9097
type: 'number',
91-
description: 'Percentage of searches that result in clicks',
98+
description: 'Percentage of searches that result in an organic click',
99+
optional: true,
100+
},
101+
parentTopic: {
102+
type: 'string',
103+
description: 'The parent topic for this keyword',
104+
optional: true,
92105
},
93-
parentTopic: { type: 'string', description: 'The parent topic for this keyword' },
94106
trafficPotential: {
95107
type: 'number',
96108
description: 'Estimated traffic potential if ranking #1',
109+
optional: true,
97110
},
98111
},
99112
},

0 commit comments

Comments
 (0)