chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import type { AhrefsAnchorsParams, AhrefsAnchorsResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'anchor,links_to_target,dofollow_links,refdomains,first_seen,last_seen'
|
||||
|
||||
export const anchorsTool: ToolConfig<AhrefsAnchorsParams, AhrefsAnchorsResponse> = {
|
||||
id: 'ahrefs_anchors',
|
||||
name: 'Ahrefs Anchors',
|
||||
description:
|
||||
"Get the anchor text distribution for a target domain or URL's backlinks, showing how many links and referring domains use each anchor text.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
history: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Historical scope: "live" (currently live), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/anchors')
|
||||
url.searchParams.set('target', params.target)
|
||||
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))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get anchors')
|
||||
}
|
||||
|
||||
const anchors = (data.anchors || []).map((item: any) => ({
|
||||
anchor: item.anchor || '',
|
||||
backlinks: item.links_to_target ?? 0,
|
||||
dofollowBacklinks: item.dofollow_links ?? 0,
|
||||
referringDomains: item.refdomains ?? 0,
|
||||
firstSeen: item.first_seen || '',
|
||||
lastSeen: item.last_seen ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
anchors,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
anchors: {
|
||||
type: 'array',
|
||||
description: 'Anchor text distribution for the backlink profile',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
anchor: { type: 'string', description: 'The anchor text' },
|
||||
backlinks: { type: 'number', description: 'Total backlinks using this anchor text' },
|
||||
dofollowBacklinks: {
|
||||
type: 'number',
|
||||
description: 'Number of dofollow backlinks using this anchor text',
|
||||
},
|
||||
referringDomains: {
|
||||
type: 'number',
|
||||
description: 'Number of unique referring domains using this anchor text',
|
||||
},
|
||||
firstSeen: {
|
||||
type: 'string',
|
||||
description: 'When a link with this anchor was first found',
|
||||
},
|
||||
lastSeen: {
|
||||
type: 'string',
|
||||
description: 'When a backlink with this anchor was last seen (null if still live)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
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',
|
||||
description:
|
||||
'Get a list of backlinks pointing to a target domain or URL. Returns details about each backlink including source URL, anchor text, and domain rating.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
history: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Historical scope: "live" (currently live backlinks), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date).',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/all-backlinks')
|
||||
url.searchParams.set('target', params.target)
|
||||
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))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get backlinks')
|
||||
}
|
||||
|
||||
const backlinks = (data.backlinks || []).map((link: any) => ({
|
||||
urlFrom: link.url_from || '',
|
||||
urlTo: link.url_to || '',
|
||||
anchor: link.anchor || '',
|
||||
domainRatingSource: link.domain_rating_source ?? 0,
|
||||
isDofollow: link.is_dofollow ?? false,
|
||||
firstSeen: link.first_seen || '',
|
||||
lastVisited: link.last_visited || '',
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
backlinks,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
backlinks: {
|
||||
type: 'array',
|
||||
description: 'List of backlinks pointing to the target',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urlFrom: { type: 'string', description: 'The URL of the page containing the backlink' },
|
||||
urlTo: { type: 'string', description: 'The URL being linked to' },
|
||||
anchor: { type: 'string', description: 'The anchor text of the link' },
|
||||
domainRatingSource: {
|
||||
type: 'number',
|
||||
description: 'Domain Rating of the linking domain',
|
||||
},
|
||||
isDofollow: { type: 'boolean', description: 'Whether the link is dofollow' },
|
||||
firstSeen: { type: 'string', description: 'When the backlink was first discovered' },
|
||||
lastVisited: { type: 'string', description: 'When the backlink was last checked' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import type { AhrefsBacklinksStatsParams, AhrefsBacklinksStatsResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const backlinksStatsTool: ToolConfig<
|
||||
AhrefsBacklinksStatsParams,
|
||||
AhrefsBacklinksStatsResponse
|
||||
> = {
|
||||
id: 'ahrefs_backlinks_stats',
|
||||
name: 'Ahrefs Backlinks Stats',
|
||||
description:
|
||||
'Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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 to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks-stats')
|
||||
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)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
|
||||
}
|
||||
|
||||
const metrics = data.metrics || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
stats: {
|
||||
liveBacklinks: metrics.live ?? 0,
|
||||
liveReferringDomains: metrics.live_refdomains ?? 0,
|
||||
allTimeBacklinks: metrics.all_time ?? 0,
|
||||
allTimeReferringDomains: metrics.all_time_refdomains ?? 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
stats: {
|
||||
type: 'object',
|
||||
description: 'Backlink and referring domain totals',
|
||||
properties: {
|
||||
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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import type { AhrefsBatchAnalysisParams, AhrefsBatchAnalysisResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'url,domain_rating,ahrefs_rank,backlinks,refdomains,org_traffic,org_keywords,paid_traffic'
|
||||
|
||||
export const batchAnalysisTool: ToolConfig<AhrefsBatchAnalysisParams, AhrefsBatchAnalysisResponse> =
|
||||
{
|
||||
id: 'ahrefs_batch_analysis',
|
||||
name: 'Ahrefs Batch Analysis',
|
||||
description:
|
||||
'Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traffic, and more) for multiple domains or URLs in a single request. Useful for comparing many competitors at once.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
targets: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated list of domains or URLs to analyze. Example: "example.com,competitor.com"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode applied to every target: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
protocol: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Protocol applied to every target: "both" (default), "http", or "https"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
volumeMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search volume calculation: "monthly" or "average" (default: "monthly")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://api.ahrefs.com/v3/batch-analysis/batch-analysis',
|
||||
method: 'POST',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
body: (params) => {
|
||||
const targets = params.targets
|
||||
.split(',')
|
||||
.map((target) => target.trim())
|
||||
.filter((target) => target.length > 0)
|
||||
.map((url) => ({
|
||||
url,
|
||||
mode: params.mode || 'subdomains',
|
||||
protocol: params.protocol || 'both',
|
||||
}))
|
||||
|
||||
return {
|
||||
select: SELECT_FIELDS.split(','),
|
||||
targets,
|
||||
country: params.country || 'us',
|
||||
volume_mode: params.volumeMode || 'monthly',
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to run batch analysis')
|
||||
}
|
||||
|
||||
const results = (data.targets || []).map((item: any) => ({
|
||||
url: item.url || '',
|
||||
index: item.index ?? 0,
|
||||
domainRating: item.domain_rating ?? null,
|
||||
ahrefsRank: item.ahrefs_rank ?? null,
|
||||
backlinks: item.backlinks ?? null,
|
||||
referringDomains: item.refdomains ?? null,
|
||||
organicTraffic: item.org_traffic ?? null,
|
||||
organicKeywords: item.org_keywords ?? null,
|
||||
paidTraffic: item.paid_traffic ?? null,
|
||||
error: item.error ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
results,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
results: {
|
||||
type: 'array',
|
||||
description: 'Bulk metrics for each analyzed target, in submission order',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'The analyzed target URL or domain' },
|
||||
index: { type: 'number', description: 'Index of the target in the submitted list' },
|
||||
domainRating: {
|
||||
type: 'number',
|
||||
description: 'Domain Rating score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
ahrefsRank: {
|
||||
type: 'number',
|
||||
description: 'Ahrefs Rank (global ranking)',
|
||||
optional: true,
|
||||
},
|
||||
backlinks: {
|
||||
type: 'number',
|
||||
description: 'Total backlinks to the target',
|
||||
optional: true,
|
||||
},
|
||||
referringDomains: {
|
||||
type: 'number',
|
||||
description: 'Unique domains linking to the target',
|
||||
optional: true,
|
||||
},
|
||||
organicTraffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic traffic',
|
||||
optional: true,
|
||||
},
|
||||
organicKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of organic keywords ranked (top 100)',
|
||||
optional: true,
|
||||
},
|
||||
paidTraffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid search traffic',
|
||||
optional: true,
|
||||
},
|
||||
error: {
|
||||
type: 'string',
|
||||
description: 'Error message if this target could not be analyzed',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type {
|
||||
AhrefsBrokenBacklinksParams,
|
||||
AhrefsBrokenBacklinksResponse,
|
||||
} 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
|
||||
> = {
|
||||
id: 'ahrefs_broken_backlinks',
|
||||
name: 'Ahrefs Broken Backlinks',
|
||||
description:
|
||||
'Get a list of broken backlinks pointing to a target domain or URL. Useful for identifying link reclamation opportunities.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
|
||||
url.searchParams.set('target', params.target)
|
||||
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))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
|
||||
}
|
||||
|
||||
const brokenBacklinks = (data.backlinks || []).map((link: any) => ({
|
||||
urlFrom: link.url_from || '',
|
||||
urlTo: link.url_to || '',
|
||||
httpCode: link.http_code_target ?? null,
|
||||
anchor: link.anchor || '',
|
||||
domainRatingSource: link.domain_rating_source ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
brokenBacklinks,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
brokenBacklinks: {
|
||||
type: 'array',
|
||||
description: 'List of broken backlinks',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
urlFrom: {
|
||||
type: 'string',
|
||||
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 of the broken target URL (e.g., 404, 410)',
|
||||
optional: true,
|
||||
},
|
||||
anchor: { type: 'string', description: 'The anchor text of the link' },
|
||||
domainRatingSource: {
|
||||
type: 'number',
|
||||
description: 'Domain Rating of the linking domain',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { AhrefsDomainRatingParams, AhrefsDomainRatingResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomainRatingResponse> = {
|
||||
id: 'ahrefs_domain_rating',
|
||||
name: 'Ahrefs Domain Rating',
|
||||
description:
|
||||
"Get the Domain Rating (DR) and Ahrefs Rank for a target domain. Domain Rating shows the strength of a website's backlink profile on a scale from 0 to 100.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain to analyze (e.g., example.com)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/domain-rating')
|
||||
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)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get domain rating')
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
domainRating: data.domain_rating?.domain_rating ?? 0,
|
||||
ahrefsRank: data.domain_rating?.ahrefs_rank ?? null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
domainRating: {
|
||||
type: 'number',
|
||||
description: 'Domain Rating score (0-100)',
|
||||
},
|
||||
ahrefsRank: {
|
||||
type: 'number',
|
||||
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import type {
|
||||
AhrefsDomainRatingHistoryParams,
|
||||
AhrefsDomainRatingHistoryResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const domainRatingHistoryTool: ToolConfig<
|
||||
AhrefsDomainRatingHistoryParams,
|
||||
AhrefsDomainRatingHistoryResponse
|
||||
> = {
|
||||
id: 'ahrefs_domain_rating_history',
|
||||
name: 'Ahrefs Domain Rating History',
|
||||
description:
|
||||
'Get the historical Domain Rating (DR) trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
dateFrom: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Start date of the historical period, in YYYY-MM-DD format',
|
||||
},
|
||||
dateTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'End date of the historical period, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
historyGrouping: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Time interval for grouping data points: "daily", "weekly", or "monthly" (default: "monthly")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/domain-rating-history')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('date_from', params.dateFrom)
|
||||
if (params.dateTo) url.searchParams.set('date_to', params.dateTo)
|
||||
if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get domain rating history')
|
||||
}
|
||||
|
||||
const domainRatings = (data.domain_ratings || []).map((item: any) => ({
|
||||
date: item.date || '',
|
||||
domainRating: item.domain_rating ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
domainRatings,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
domainRatings: {
|
||||
type: 'array',
|
||||
description: 'Historical Domain Rating data points',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'The date of the measurement' },
|
||||
domainRating: { type: 'number', description: 'Domain Rating score (0-100) on this date' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { anchorsTool } from '@/tools/ahrefs/anchors'
|
||||
import { backlinksTool } from '@/tools/ahrefs/backlinks'
|
||||
import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
|
||||
import { batchAnalysisTool } from '@/tools/ahrefs/batch_analysis'
|
||||
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
|
||||
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
|
||||
import { domainRatingHistoryTool } from '@/tools/ahrefs/domain_rating_history'
|
||||
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
|
||||
import { keywordsHistoryTool } from '@/tools/ahrefs/keywords_history'
|
||||
import { metricsTool } from '@/tools/ahrefs/metrics'
|
||||
import { metricsHistoryTool } from '@/tools/ahrefs/metrics_history'
|
||||
import { organicCompetitorsTool } from '@/tools/ahrefs/organic_competitors'
|
||||
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
|
||||
import { paidPagesTool } from '@/tools/ahrefs/paid_pages'
|
||||
import { rankTrackerCompetitorsOverviewTool } from '@/tools/ahrefs/rank_tracker_competitors_overview'
|
||||
import { rankTrackerCompetitorsStatsTool } from '@/tools/ahrefs/rank_tracker_competitors_stats'
|
||||
import { rankTrackerOverviewTool } from '@/tools/ahrefs/rank_tracker_overview'
|
||||
import { rankTrackerSerpOverviewTool } from '@/tools/ahrefs/rank_tracker_serp_overview'
|
||||
import { refdomainsHistoryTool } from '@/tools/ahrefs/refdomains_history'
|
||||
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
|
||||
import { relatedTermsTool } from '@/tools/ahrefs/related_terms'
|
||||
import { siteAuditPageExplorerTool } from '@/tools/ahrefs/site_audit_page_explorer'
|
||||
import { topPagesTool } from '@/tools/ahrefs/top_pages'
|
||||
|
||||
export const ahrefsDomainRatingTool = domainRatingTool
|
||||
export const ahrefsBacklinksTool = backlinksTool
|
||||
export const ahrefsBacklinksStatsTool = backlinksStatsTool
|
||||
export const ahrefsReferringDomainsTool = referringDomainsTool
|
||||
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 const ahrefsRankTrackerOverviewTool = rankTrackerOverviewTool
|
||||
export const ahrefsRankTrackerSerpOverviewTool = rankTrackerSerpOverviewTool
|
||||
export const ahrefsRankTrackerCompetitorsOverviewTool = rankTrackerCompetitorsOverviewTool
|
||||
export const ahrefsRankTrackerCompetitorsStatsTool = rankTrackerCompetitorsStatsTool
|
||||
export const ahrefsBatchAnalysisTool = batchAnalysisTool
|
||||
export const ahrefsSiteAuditPageExplorerTool = siteAuditPageExplorerTool
|
||||
export const ahrefsDomainRatingHistoryTool = domainRatingHistoryTool
|
||||
export const ahrefsMetricsHistoryTool = metricsHistoryTool
|
||||
export const ahrefsRefdomainsHistoryTool = refdomainsHistoryTool
|
||||
export const ahrefsKeywordsHistoryTool = keywordsHistoryTool
|
||||
export const ahrefsRelatedTermsTool = relatedTermsTool
|
||||
export const ahrefsAnchorsTool = anchorsTool
|
||||
export const ahrefsPaidPagesTool = paidPagesTool
|
||||
|
||||
export * from '@/tools/ahrefs/types'
|
||||
@@ -0,0 +1,129 @@
|
||||
import type {
|
||||
AhrefsKeywordOverviewParams,
|
||||
AhrefsKeywordOverviewResponse,
|
||||
} 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
|
||||
> = {
|
||||
id: 'ahrefs_keyword_overview',
|
||||
name: 'Ahrefs Keyword Overview',
|
||||
description:
|
||||
'Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The keyword to analyze',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for keyword data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
|
||||
url.searchParams.set('keywords', params.keyword)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
|
||||
}
|
||||
|
||||
const result = (data.keywords || [])[0] || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
overview: {
|
||||
keyword: result.keyword || '',
|
||||
searchVolume: result.volume ?? 0,
|
||||
keywordDifficulty: result.difficulty ?? null,
|
||||
cpc: typeof result.cpc === 'number' ? result.cpc / 100 : 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
overview: {
|
||||
type: 'object',
|
||||
description: 'Keyword metrics overview',
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The analyzed keyword' },
|
||||
searchVolume: { type: 'number', description: 'Monthly search volume' },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
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 an organic click',
|
||||
optional: true,
|
||||
},
|
||||
parentTopic: {
|
||||
type: 'string',
|
||||
description: 'The parent topic for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import type {
|
||||
AhrefsKeywordsHistoryParams,
|
||||
AhrefsKeywordsHistoryResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'date,top3,top4_10,top11_20,top21_50,top51_plus'
|
||||
|
||||
export const keywordsHistoryTool: ToolConfig<
|
||||
AhrefsKeywordsHistoryParams,
|
||||
AhrefsKeywordsHistoryResponse
|
||||
> = {
|
||||
id: 'ahrefs_keywords_history',
|
||||
name: 'Ahrefs Keywords History',
|
||||
description:
|
||||
'Get the historical organic keyword ranking distribution for a target domain or URL over a date range: how many keywords rank in each position bucket at each point in time.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
dateFrom: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Start date of the historical period, in YYYY-MM-DD format',
|
||||
},
|
||||
dateTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'End date of the historical period, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
historyGrouping: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Time interval for grouping data points: "daily", "weekly", or "monthly" (default: "monthly")',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for search results. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/keywords-history')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('date_from', params.dateFrom)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping)
|
||||
if (params.dateTo) url.searchParams.set('date_to', params.dateTo)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get keywords history')
|
||||
}
|
||||
|
||||
const keywordsHistory = (data.keywords || []).map((item: any) => ({
|
||||
date: item.date || '',
|
||||
top3: item.top3 ?? 0,
|
||||
top4To10: item.top4_10 ?? 0,
|
||||
top11To20: item.top11_20 ?? 0,
|
||||
top21To50: item.top21_50 ?? 0,
|
||||
top51Plus: item.top51_plus ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
keywordsHistory,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
keywordsHistory: {
|
||||
type: 'array',
|
||||
description: 'Historical organic keyword ranking distribution',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'Date of the record' },
|
||||
top3: { type: 'number', description: 'Keywords ranking in top 3 organic results' },
|
||||
top4To10: { type: 'number', description: 'Keywords ranking in positions 4-10' },
|
||||
top11To20: { type: 'number', description: 'Keywords ranking in positions 11-20' },
|
||||
top21To50: { type: 'number', description: 'Keywords ranking in positions 21-50' },
|
||||
top51Plus: { type: 'number', description: 'Keywords ranking in position 51 and beyond' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { AhrefsMetricsParams, AhrefsMetricsResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const metricsTool: ToolConfig<AhrefsMetricsParams, AhrefsMetricsResponse> = {
|
||||
id: 'ahrefs_metrics',
|
||||
name: 'Ahrefs Metrics',
|
||||
description:
|
||||
'Get a one-call organic and paid search overview for a target domain or URL: organic traffic, organic keywords, paid traffic, paid keywords, and estimated traffic cost.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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 to report metrics on, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/metrics')
|
||||
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('country', params.country || 'us')
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get metrics')
|
||||
}
|
||||
|
||||
const metrics = data.metrics || {}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
metrics: {
|
||||
organicTraffic: metrics.org_traffic ?? 0,
|
||||
organicKeywords: metrics.org_keywords ?? 0,
|
||||
organicKeywordsTop3: metrics.org_keywords_1_3 ?? 0,
|
||||
organicCost: typeof metrics.org_cost === 'number' ? metrics.org_cost / 100 : null,
|
||||
paidTraffic: metrics.paid_traffic ?? 0,
|
||||
paidKeywords: metrics.paid_keywords ?? 0,
|
||||
paidPages: metrics.paid_pages ?? 0,
|
||||
paidCost: typeof metrics.paid_cost === 'number' ? metrics.paid_cost / 100 : null,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
metrics: {
|
||||
type: 'object',
|
||||
description: 'Organic and paid search overview',
|
||||
properties: {
|
||||
organicTraffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
organicKeywords: { type: 'number', description: 'Number of organic keywords ranked' },
|
||||
organicKeywordsTop3: {
|
||||
type: 'number',
|
||||
description: 'Number of organic keywords ranking in positions 1-3',
|
||||
},
|
||||
organicCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly cost to replicate organic traffic via ads (USD)',
|
||||
optional: true,
|
||||
},
|
||||
paidTraffic: { type: 'number', description: 'Estimated monthly paid search traffic' },
|
||||
paidKeywords: { type: 'number', description: 'Number of paid keywords targeted' },
|
||||
paidPages: { type: 'number', description: 'Number of pages receiving paid traffic' },
|
||||
paidCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid search spend (USD)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { AhrefsMetricsHistoryParams, AhrefsMetricsHistoryResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'date,org_traffic,org_cost,paid_traffic,paid_cost'
|
||||
|
||||
export const metricsHistoryTool: ToolConfig<
|
||||
AhrefsMetricsHistoryParams,
|
||||
AhrefsMetricsHistoryResponse
|
||||
> = {
|
||||
id: 'ahrefs_metrics_history',
|
||||
name: 'Ahrefs Metrics History',
|
||||
description:
|
||||
'Get the historical organic and paid traffic trend for a target domain or URL over a date range: organic traffic/cost and paid traffic/cost at each point in time.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
dateFrom: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Start date of the historical period, in YYYY-MM-DD format',
|
||||
},
|
||||
dateTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'End date of the historical period, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
volumeMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search volume calculation: "monthly" or "average" (default: "monthly")',
|
||||
},
|
||||
historyGrouping: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Time interval for grouping data points: "daily", "weekly", or "monthly" (default: "monthly")',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/metrics-history')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('date_from', params.dateFrom)
|
||||
if (params.dateTo) url.searchParams.set('date_to', params.dateTo)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
url.searchParams.set('volume_mode', params.volumeMode || 'monthly')
|
||||
if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get metrics history')
|
||||
}
|
||||
|
||||
const metrics = (data.metrics || []).map((item: any) => ({
|
||||
date: item.date || '',
|
||||
organicTraffic: item.org_traffic ?? 0,
|
||||
organicCost: typeof item.org_cost === 'number' ? item.org_cost / 100 : null,
|
||||
paidTraffic: item.paid_traffic ?? 0,
|
||||
paidCost: typeof item.paid_cost === 'number' ? item.paid_cost / 100 : null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
metricsHistory: metrics,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
metricsHistory: {
|
||||
type: 'array',
|
||||
description: 'Historical organic and paid traffic data points',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'Date of the metric entry' },
|
||||
organicTraffic: { type: 'number', description: 'Estimated monthly organic visits' },
|
||||
organicCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly cost to replicate organic traffic via ads (USD)',
|
||||
optional: true,
|
||||
},
|
||||
paidTraffic: { type: 'number', description: 'Estimated monthly paid search visits' },
|
||||
paidCost: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid search spend (USD)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type {
|
||||
AhrefsOrganicCompetitorsParams,
|
||||
AhrefsOrganicCompetitorsResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'competitor_domain,domain_rating,keywords_common,keywords_target,keywords_competitor,traffic'
|
||||
|
||||
export const organicCompetitorsTool: ToolConfig<
|
||||
AhrefsOrganicCompetitorsParams,
|
||||
AhrefsOrganicCompetitorsResponse
|
||||
> = {
|
||||
id: 'ahrefs_organic_competitors',
|
||||
name: 'Ahrefs Organic Competitors',
|
||||
description:
|
||||
'Get domains that compete with a target domain or URL for the same organic keywords, ranked by keyword overlap.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for search results. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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 to report metrics on, 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: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/organic-competitors')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get organic competitors')
|
||||
}
|
||||
|
||||
const competitors = (data.competitors || []).map((competitor: any) => ({
|
||||
domain: competitor.competitor_domain ?? null,
|
||||
domainRating: competitor.domain_rating ?? 0,
|
||||
commonKeywords: competitor.keywords_common ?? 0,
|
||||
targetKeywords: competitor.keywords_target ?? 0,
|
||||
competitorKeywords: competitor.keywords_competitor ?? 0,
|
||||
traffic: competitor.traffic ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
competitors,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
competitors: {
|
||||
type: 'array',
|
||||
description: 'List of organic search competitors ranked by keyword overlap',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain: {
|
||||
type: 'string',
|
||||
description: 'The competitor domain',
|
||||
optional: true,
|
||||
},
|
||||
domainRating: { type: 'number', description: 'Domain Rating of the competitor' },
|
||||
commonKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the competitor and target both rank for',
|
||||
},
|
||||
targetKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the target ranks for',
|
||||
},
|
||||
competitorKeywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the competitor ranks for',
|
||||
},
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic traffic for the competitor',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type {
|
||||
AhrefsOrganicKeywordsParams,
|
||||
AhrefsOrganicKeywordsResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'keyword,volume,best_position,best_position_url,sum_traffic,keyword_difficulty'
|
||||
|
||||
export const organicKeywordsTool: ToolConfig<
|
||||
AhrefsOrganicKeywordsParams,
|
||||
AhrefsOrganicKeywordsResponse
|
||||
> = {
|
||||
id: 'ahrefs_organic_keywords',
|
||||
name: 'Ahrefs Organic Keywords',
|
||||
description:
|
||||
'Get organic keywords that a target domain or URL ranks for in Google search results. Returns keyword details including search volume, ranking position, and estimated traffic.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for search results. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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 to report metrics on, 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: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/organic-keywords')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get organic keywords')
|
||||
}
|
||||
|
||||
const keywords = (data.keywords || []).map((kw: any) => ({
|
||||
keyword: kw.keyword || '',
|
||||
volume: kw.volume ?? 0,
|
||||
position: kw.best_position ?? null,
|
||||
url: kw.best_position_url ?? null,
|
||||
traffic: kw.sum_traffic ?? 0,
|
||||
keywordDifficulty: kw.keyword_difficulty ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
keywords,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
keywords: {
|
||||
type: 'array',
|
||||
description: 'List of organic keywords the target ranks for',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The keyword' },
|
||||
volume: { type: 'number', description: 'Monthly search volume' },
|
||||
position: {
|
||||
type: 'number',
|
||||
description: 'Best ranking position for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
description: 'The URL that ranks at the best position for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { AhrefsPaidPagesParams, AhrefsPaidPagesResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'url,sum_traffic,keywords,top_keyword,value,ads_count'
|
||||
|
||||
export const paidPagesTool: ToolConfig<AhrefsPaidPagesParams, AhrefsPaidPagesResponse> = {
|
||||
id: 'ahrefs_paid_pages',
|
||||
name: 'Ahrefs Paid Pages',
|
||||
description:
|
||||
"Get a target domain's pages that receive paid search traffic, sorted by estimated paid traffic. Returns page URLs with their paid traffic, keyword counts, and estimated spend.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report metrics on, 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: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/paid-pages')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// 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('country', params.country || 'us')
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get paid pages')
|
||||
}
|
||||
|
||||
const paidPages = (data.pages || []).map((page: any) => ({
|
||||
url: page.url ?? null,
|
||||
traffic: page.sum_traffic ?? null,
|
||||
keywords: page.keywords ?? null,
|
||||
topKeyword: page.top_keyword ?? null,
|
||||
value: typeof page.value === 'number' ? page.value / 100 : null,
|
||||
adsCount: page.ads_count ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
paidPages,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
paidPages: {
|
||||
type: 'array',
|
||||
description: 'List of pages receiving paid search traffic',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'The page URL', optional: true },
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid search traffic',
|
||||
optional: true,
|
||||
},
|
||||
keywords: {
|
||||
type: 'number',
|
||||
description: 'Number of paid keywords the page ranks for',
|
||||
optional: true,
|
||||
},
|
||||
topKeyword: {
|
||||
type: 'string',
|
||||
description: 'The top keyword driving paid traffic to this page',
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly paid traffic cost in USD',
|
||||
optional: true,
|
||||
},
|
||||
adsCount: {
|
||||
type: 'number',
|
||||
description: 'Number of unique ads shown for this page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
import type {
|
||||
AhrefsRankTrackerCompetitorsOverviewParams,
|
||||
AhrefsRankTrackerCompetitorsOverviewResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'keyword,volume,keyword_difficulty,serp_features,competitors_list'
|
||||
|
||||
export const rankTrackerCompetitorsOverviewTool: ToolConfig<
|
||||
AhrefsRankTrackerCompetitorsOverviewParams,
|
||||
AhrefsRankTrackerCompetitorsOverviewResponse
|
||||
> = {
|
||||
id: 'ahrefs_rank_tracker_competitors_overview',
|
||||
name: 'Ahrefs Rank Tracker Competitors Overview',
|
||||
description:
|
||||
"Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker project: each tracked keyword's volume and difficulty alongside every competitor's position, traffic, and traffic value. This endpoint is free and does not consume API units.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The Rank Tracker project ID (found in the project URL in Ahrefs)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report rankings for, in YYYY-MM-DD format',
|
||||
},
|
||||
device: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rankings device type: "desktop" or "mobile"',
|
||||
},
|
||||
dateCompared: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comparison date in YYYY-MM-DD format, to compute position/traffic deltas',
|
||||
},
|
||||
volumeMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search volume calculation: "monthly" or "average" (default: "monthly")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/rank-tracker/competitors-overview')
|
||||
url.searchParams.set('project_id', String(params.projectId))
|
||||
url.searchParams.set('date', params.date)
|
||||
url.searchParams.set('device', params.device)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.dateCompared) url.searchParams.set('date_compared', params.dateCompared)
|
||||
url.searchParams.set('volume_mode', params.volumeMode || 'monthly')
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
data.error?.message || data.error || 'Failed to get rank tracker competitors overview'
|
||||
)
|
||||
}
|
||||
|
||||
const competitorKeywords = (data.keywords || []).map((item: any) => ({
|
||||
keyword: item.keyword || '',
|
||||
volume: item.volume ?? null,
|
||||
keywordDifficulty: item.keyword_difficulty ?? null,
|
||||
serpFeatures: item.serp_features ?? [],
|
||||
competitorsList: (item.competitors_list || []).map((competitor: any) => ({
|
||||
url: competitor.url || '',
|
||||
position: competitor.position ?? null,
|
||||
bestPositionKind: competitor.best_position_kind ?? null,
|
||||
traffic: competitor.traffic ?? null,
|
||||
value: typeof competitor.value === 'number' ? competitor.value / 100 : null,
|
||||
})),
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
competitorKeywords,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
competitorKeywords: {
|
||||
type: 'array',
|
||||
description: 'Tracked keywords with competitor ranking data',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The tracked keyword' },
|
||||
volume: { type: 'number', description: 'Average monthly search volume', optional: true },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
serpFeatures: {
|
||||
type: 'array',
|
||||
description: 'SERP features present in the results',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
competitorsList: {
|
||||
type: 'array',
|
||||
description: 'Ranking data for each tracked competitor on this keyword',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: "The competitor's ranking URL" },
|
||||
position: {
|
||||
type: 'number',
|
||||
description: 'Current ranking position',
|
||||
optional: true,
|
||||
},
|
||||
bestPositionKind: {
|
||||
type: 'string',
|
||||
description: 'Type of the best position achieved',
|
||||
optional: true,
|
||||
},
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated traffic to the competitor',
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Estimated traffic value (USD)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import type {
|
||||
AhrefsRankTrackerCompetitorsStatsParams,
|
||||
AhrefsRankTrackerCompetitorsStatsResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'competitor,traffic,traffic_value,average_position,pos_1_3,pos_4_10,share_of_voice,share_of_traffic_value'
|
||||
|
||||
export const rankTrackerCompetitorsStatsTool: ToolConfig<
|
||||
AhrefsRankTrackerCompetitorsStatsParams,
|
||||
AhrefsRankTrackerCompetitorsStatsResponse
|
||||
> = {
|
||||
id: 'ahrefs_rank_tracker_competitors_stats',
|
||||
name: 'Ahrefs Rank Tracker Competitors Stats',
|
||||
description:
|
||||
"Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competitor's traffic, traffic value, average position, and share of voice across all tracked keywords. This endpoint is free and does not consume API units.",
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The Rank Tracker project ID (found in the project URL in Ahrefs)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report metrics for, in YYYY-MM-DD format',
|
||||
},
|
||||
device: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rankings device type: "desktop" or "mobile"',
|
||||
},
|
||||
volumeMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search volume calculation: "monthly" or "average" (default: "monthly")',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/rank-tracker/competitors-stats')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
url.searchParams.set('date', params.date)
|
||||
url.searchParams.set('device', params.device)
|
||||
url.searchParams.set('project_id', String(params.projectId))
|
||||
url.searchParams.set('volume_mode', params.volumeMode || 'monthly')
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
data.error?.message || data.error || 'Failed to get rank tracker competitors stats'
|
||||
)
|
||||
}
|
||||
|
||||
const competitorsStats = (data['competitors-metrics'] || []).map((item: any) => ({
|
||||
competitor: item.competitor || '',
|
||||
traffic: item.traffic ?? null,
|
||||
trafficValue: typeof item.traffic_value === 'number' ? item.traffic_value / 100 : null,
|
||||
averagePosition: item.average_position ?? null,
|
||||
pos1To3: item.pos_1_3 ?? 0,
|
||||
pos4To10: item.pos_4_10 ?? 0,
|
||||
shareOfVoice: item.share_of_voice ?? 0,
|
||||
shareOfTrafficValue: item.share_of_traffic_value ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
competitorsStats,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
competitorsStats: {
|
||||
type: 'array',
|
||||
description: 'Aggregate stats for each tracked competitor',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
competitor: { type: 'string', description: "The competitor's URL" },
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic visits',
|
||||
optional: true,
|
||||
},
|
||||
trafficValue: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic traffic value (USD)',
|
||||
optional: true,
|
||||
},
|
||||
averagePosition: {
|
||||
type: 'number',
|
||||
description: 'Average top organic position across tracked keywords',
|
||||
optional: true,
|
||||
},
|
||||
pos1To3: { type: 'number', description: 'Keywords ranking in top 3 positions' },
|
||||
pos4To10: { type: 'number', description: 'Keywords ranking in positions 4-10' },
|
||||
shareOfVoice: { type: 'number', description: 'Organic traffic share percentage' },
|
||||
shareOfTrafficValue: {
|
||||
type: 'number',
|
||||
description: 'Organic traffic value share percentage',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import type {
|
||||
AhrefsRankTrackerOverviewParams,
|
||||
AhrefsRankTrackerOverviewResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'keyword,position,volume,keyword_difficulty,url,traffic,serp_features,best_position_kind'
|
||||
|
||||
export const rankTrackerOverviewTool: ToolConfig<
|
||||
AhrefsRankTrackerOverviewParams,
|
||||
AhrefsRankTrackerOverviewResponse
|
||||
> = {
|
||||
id: 'ahrefs_rank_tracker_overview',
|
||||
name: 'Ahrefs Rank Tracker Overview',
|
||||
description:
|
||||
'Get ranking overview metrics for the keywords tracked in an Ahrefs Rank Tracker project: position, search volume, keyword difficulty, and estimated traffic. This endpoint is free and does not consume API units.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The Rank Tracker project ID (found in the project URL in Ahrefs)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Date to report rankings for, in YYYY-MM-DD format',
|
||||
},
|
||||
device: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rankings device type: "desktop" or "mobile"',
|
||||
},
|
||||
dateCompared: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Comparison date in YYYY-MM-DD format, to compute position/traffic deltas',
|
||||
},
|
||||
volumeMode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Search volume calculation: "monthly" or "average" (default: "monthly")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/rank-tracker/overview')
|
||||
url.searchParams.set('project_id', String(params.projectId))
|
||||
url.searchParams.set('date', params.date)
|
||||
url.searchParams.set('device', params.device)
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.dateCompared) url.searchParams.set('date_compared', params.dateCompared)
|
||||
url.searchParams.set('volume_mode', params.volumeMode || 'monthly')
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get rank tracker overview')
|
||||
}
|
||||
|
||||
const overviews = (data.overviews || []).map((item: any) => ({
|
||||
keyword: item.keyword || '',
|
||||
position: item.position ?? null,
|
||||
volume: item.volume ?? null,
|
||||
keywordDifficulty: item.keyword_difficulty ?? null,
|
||||
url: item.url ?? null,
|
||||
traffic: item.traffic ?? null,
|
||||
serpFeatures: item.serp_features ?? [],
|
||||
bestPositionKind: item.best_position_kind ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
overviews,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
overviews: {
|
||||
type: 'array',
|
||||
description: 'Ranking overview for each tracked keyword',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The tracked keyword' },
|
||||
position: {
|
||||
type: 'number',
|
||||
description: 'Top organic search position',
|
||||
optional: true,
|
||||
},
|
||||
volume: { type: 'number', description: 'Average monthly search volume', optional: true },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
url: { type: 'string', description: 'Top-ranking URL', optional: true },
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic visits',
|
||||
optional: true,
|
||||
},
|
||||
serpFeatures: {
|
||||
type: 'array',
|
||||
description: 'SERP features present in the results',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
bestPositionKind: {
|
||||
type: 'string',
|
||||
description: 'Type of the top position (organic, paid, or SERP feature)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
import type {
|
||||
AhrefsRankTrackerSerpOverviewParams,
|
||||
AhrefsRankTrackerSerpOverviewResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const rankTrackerSerpOverviewTool: ToolConfig<
|
||||
AhrefsRankTrackerSerpOverviewParams,
|
||||
AhrefsRankTrackerSerpOverviewResponse
|
||||
> = {
|
||||
id: 'ahrefs_rank_tracker_serp_overview',
|
||||
name: 'Ahrefs Rank Tracker SERP Overview',
|
||||
description:
|
||||
'Get the full SERP (search engine results page) for a keyword tracked in an Ahrefs Rank Tracker project, including every ranking URL with its position, title, and authority metrics. This endpoint is free and does not consume API units.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The Rank Tracker project ID (found in the project URL in Ahrefs)',
|
||||
},
|
||||
keyword: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The tracked keyword to retrieve SERP data for',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for the tracked keyword. Example: "us", "gb", "de"',
|
||||
},
|
||||
device: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Rankings device type: "desktop" or "mobile"',
|
||||
},
|
||||
topPositions: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of top organic positions to return (defaults to all available)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Timestamp to return the last available SERP Overview at, in YYYY-MM-DDThh:mm:ss format',
|
||||
},
|
||||
locationId: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Location ID of the tracked keyword, if tracked at a specific location',
|
||||
},
|
||||
languageCode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Language code of the tracked keyword',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/rank-tracker/serp-overview')
|
||||
url.searchParams.set('project_id', String(params.projectId))
|
||||
url.searchParams.set('keyword', params.keyword)
|
||||
url.searchParams.set('country', params.country)
|
||||
url.searchParams.set('device', params.device)
|
||||
if (params.topPositions) url.searchParams.set('top_positions', String(params.topPositions))
|
||||
if (params.date) url.searchParams.set('date', params.date)
|
||||
if (params.locationId) url.searchParams.set('location_id', String(params.locationId))
|
||||
if (params.languageCode) url.searchParams.set('language_code', params.languageCode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get SERP overview')
|
||||
}
|
||||
|
||||
const positions = (data.positions || []).map((item: any) => ({
|
||||
position: item.position ?? 0,
|
||||
url: item.url || '',
|
||||
title: item.title || '',
|
||||
type: item.type ?? [],
|
||||
domainRating: item.domain_rating ?? 0,
|
||||
urlRating: item.url_rating ?? 0,
|
||||
backlinks: item.backlinks ?? 0,
|
||||
refdomains: item.refdomains ?? 0,
|
||||
traffic: item.traffic ?? 0,
|
||||
value: typeof item.value === 'number' ? item.value / 100 : null,
|
||||
topKeyword: item.top_keyword ?? null,
|
||||
topKeywordVolume: item.top_keyword_volume ?? null,
|
||||
updateDate: item.update_date || '',
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
positions,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
positions: {
|
||||
type: 'array',
|
||||
description: 'Every ranking result on the SERP for the tracked keyword',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
position: { type: 'number', description: 'Position of the result in the SERP' },
|
||||
url: { type: 'string', description: 'URL of the ranking page' },
|
||||
title: { type: 'string', description: 'Page title' },
|
||||
type: {
|
||||
type: 'array',
|
||||
description: 'The kind of the position: organic, paid, or a SERP feature',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
domainRating: { type: 'number', description: 'Domain Rating of the ranking domain' },
|
||||
urlRating: { type: 'number', description: 'URL Rating of the ranking page' },
|
||||
backlinks: { type: 'number', description: 'Total backlinks to the ranking domain' },
|
||||
refdomains: { type: 'number', description: 'Unique referring domains' },
|
||||
traffic: { type: 'number', description: 'Estimated monthly organic search traffic' },
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly traffic value (USD)',
|
||||
optional: true,
|
||||
},
|
||||
topKeyword: {
|
||||
type: 'string',
|
||||
description: 'Highest-traffic keyword ranking for this page',
|
||||
optional: true,
|
||||
},
|
||||
topKeywordVolume: {
|
||||
type: 'number',
|
||||
description: 'Monthly search volume for the top keyword',
|
||||
optional: true,
|
||||
},
|
||||
updateDate: { type: 'string', description: 'Date the SERP was last checked' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import type {
|
||||
AhrefsRefdomainsHistoryParams,
|
||||
AhrefsRefdomainsHistoryResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const refdomainsHistoryTool: ToolConfig<
|
||||
AhrefsRefdomainsHistoryParams,
|
||||
AhrefsRefdomainsHistoryResponse
|
||||
> = {
|
||||
id: 'ahrefs_refdomains_history',
|
||||
name: 'Ahrefs Referring Domains History',
|
||||
description:
|
||||
'Get the historical referring domains trend for a target domain or URL over a date range, grouped daily, weekly, or monthly.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain or URL to analyze. Example: "example.com"',
|
||||
},
|
||||
dateFrom: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Start date of the historical period, in YYYY-MM-DD format',
|
||||
},
|
||||
dateTo: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'End date of the historical period, in YYYY-MM-DD format (defaults to today)',
|
||||
},
|
||||
historyGrouping: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Time interval for grouping data points: "daily", "weekly", or "monthly" (default: "monthly")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/refdomains-history')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('date_from', params.dateFrom)
|
||||
if (params.dateTo) url.searchParams.set('date_to', params.dateTo)
|
||||
if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
data.error?.message || data.error || 'Failed to get referring domains history'
|
||||
)
|
||||
}
|
||||
|
||||
const referringDomainsHistory = (data.refdomains || []).map((item: any) => ({
|
||||
date: item.date || '',
|
||||
referringDomains: item.refdomains ?? 0,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
referringDomainsHistory,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
referringDomainsHistory: {
|
||||
type: 'array',
|
||||
description: 'Historical referring domains count data points',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
date: { type: 'string', description: 'The date of the data point' },
|
||||
referringDomains: {
|
||||
type: 'number',
|
||||
description: 'Total number of unique domains linking to the target on this date',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import type {
|
||||
AhrefsReferringDomainsParams,
|
||||
AhrefsReferringDomainsResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'domain,domain_rating,links_to_target,dofollow_links,first_seen,last_seen'
|
||||
|
||||
export const referringDomainsTool: ToolConfig<
|
||||
AhrefsReferringDomainsParams,
|
||||
AhrefsReferringDomainsResponse
|
||||
> = {
|
||||
id: 'ahrefs_referring_domains',
|
||||
name: 'Ahrefs Referring Domains',
|
||||
description:
|
||||
'Get a list of domains that link to a target domain or URL. Returns unique referring domains with their domain rating, backlink counts, and discovery dates.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
|
||||
},
|
||||
history: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Historical scope: "live" (currently live), "all_time" (default, includes lost domains), or "since:YYYY-MM-DD" (domains found since a date).',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/refdomains')
|
||||
url.searchParams.set('target', params.target)
|
||||
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))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get referring domains')
|
||||
}
|
||||
|
||||
const referringDomains = (data.refdomains || []).map((domain: any) => ({
|
||||
domain: domain.domain || '',
|
||||
domainRating: domain.domain_rating ?? 0,
|
||||
backlinks: domain.links_to_target ?? 0,
|
||||
dofollowBacklinks: domain.dofollow_links ?? 0,
|
||||
firstSeen: domain.first_seen || '',
|
||||
lastVisited: domain.last_seen ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
referringDomains,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
referringDomains: {
|
||||
type: 'array',
|
||||
description: 'List of domains linking to the target',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain: { type: 'string', description: 'The referring domain' },
|
||||
domainRating: { type: 'number', description: 'Domain Rating of the referring domain' },
|
||||
backlinks: {
|
||||
type: 'number',
|
||||
description: 'Total number of backlinks from this domain to the target',
|
||||
},
|
||||
dofollowBacklinks: {
|
||||
type: 'number',
|
||||
description: 'Number of dofollow backlinks from this domain',
|
||||
},
|
||||
firstSeen: { type: 'string', description: 'When the domain was first seen linking' },
|
||||
lastVisited: {
|
||||
type: 'string',
|
||||
description: 'When the domain was last seen linking (null if never re-crawled)',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import type { AhrefsRelatedTermsParams, AhrefsRelatedTermsResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'keyword,volume,difficulty,cpc,parent_topic,traffic_potential,intents,serp_features'
|
||||
|
||||
export const relatedTermsTool: ToolConfig<AhrefsRelatedTermsParams, AhrefsRelatedTermsResponse> = {
|
||||
id: 'ahrefs_related_terms',
|
||||
name: 'Ahrefs Related Terms',
|
||||
description:
|
||||
'Get keyword ideas related to a seed keyword: terms the same top-ranking pages also rank for ("also rank for") or also discuss ("also talk about"), with volume, difficulty, and CPC.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
keyword: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The seed keyword to find related terms for',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for keyword data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
terms: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Type of related keywords to return: "also_rank_for", "also_talk_about", or "all" (default: "all")',
|
||||
},
|
||||
viewFor: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Whether to derive related terms from the top 10 or top 100 ranking pages (default: "top_10")',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/related-terms')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('keywords', params.keyword)
|
||||
if (params.terms) url.searchParams.set('terms', params.terms)
|
||||
if (params.viewFor) url.searchParams.set('view_for', params.viewFor)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get related terms')
|
||||
}
|
||||
|
||||
const relatedTerms = (data.keywords || []).map((item: any) => ({
|
||||
keyword: item.keyword || '',
|
||||
volume: item.volume ?? null,
|
||||
keywordDifficulty: item.difficulty ?? null,
|
||||
cpc: typeof item.cpc === 'number' ? item.cpc / 100 : null,
|
||||
parentTopic: item.parent_topic ?? null,
|
||||
trafficPotential: item.traffic_potential ?? null,
|
||||
intents: item.intents ?? null,
|
||||
serpFeatures: item.serp_features ?? [],
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
relatedTerms,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
relatedTerms: {
|
||||
type: 'array',
|
||||
description: 'Related keyword ideas for the seed keyword',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyword: { type: 'string', description: 'The related keyword' },
|
||||
volume: { type: 'number', description: 'Average monthly search volume', optional: true },
|
||||
keywordDifficulty: {
|
||||
type: 'number',
|
||||
description: 'Keyword difficulty score (0-100)',
|
||||
optional: true,
|
||||
},
|
||||
cpc: { type: 'number', description: 'Cost per click in USD', optional: true },
|
||||
parentTopic: {
|
||||
type: 'string',
|
||||
description: 'The parent topic for this keyword',
|
||||
optional: true,
|
||||
},
|
||||
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,
|
||||
},
|
||||
serpFeatures: {
|
||||
type: 'array',
|
||||
description: 'SERP features present in the results',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import type {
|
||||
AhrefsSiteAuditPageExplorerParams,
|
||||
AhrefsSiteAuditPageExplorerResponse,
|
||||
} from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS =
|
||||
'url,http_code,title,internal_links,external_links,backlinks,compliant,traffic'
|
||||
|
||||
export const siteAuditPageExplorerTool: ToolConfig<
|
||||
AhrefsSiteAuditPageExplorerParams,
|
||||
AhrefsSiteAuditPageExplorerResponse
|
||||
> = {
|
||||
id: 'ahrefs_site_audit_page_explorer',
|
||||
name: 'Ahrefs Site Audit Page Explorer',
|
||||
description:
|
||||
'Get crawled pages from an Ahrefs Site Audit project with health and SEO metrics: HTTP status, title, link counts, backlinks, indexability, and traffic. Optionally filter to pages affected by a specific issue.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
projectId: {
|
||||
type: 'number',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The Site Audit project ID (found in the project URL in Ahrefs)',
|
||||
},
|
||||
date: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'Crawl date in YYYY-MM-DDThh:mm:ss format (defaults to the most recent crawl)',
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
|
||||
},
|
||||
offset: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Number of results to skip, for pagination',
|
||||
},
|
||||
issueId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Only return pages affected by this issue ID',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-audit/page-explorer')
|
||||
url.searchParams.set('project_id', String(params.projectId))
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
if (params.date) url.searchParams.set('date', params.date)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
if (params.offset) url.searchParams.set('offset', String(params.offset))
|
||||
if (params.issueId) url.searchParams.set('issue_id', params.issueId)
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get site audit pages')
|
||||
}
|
||||
|
||||
const auditPages = (data.pages || []).map((page: any) => ({
|
||||
url: page.url || '',
|
||||
httpCode: page.http_code ?? null,
|
||||
title: page.title ?? [],
|
||||
internalLinks: (page.internal_links || []).length,
|
||||
externalLinks: (page.external_links || []).length,
|
||||
backlinks: page.backlinks ?? null,
|
||||
compliant: page.compliant ?? null,
|
||||
traffic: page.traffic ?? null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
auditPages,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
auditPages: {
|
||||
type: 'array',
|
||||
description: 'List of crawled pages with health and SEO metrics',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'The crawled page URL' },
|
||||
httpCode: {
|
||||
type: 'number',
|
||||
description: 'HTTP status code returned by the URL',
|
||||
optional: true,
|
||||
},
|
||||
title: {
|
||||
type: 'array',
|
||||
description: 'Page title tag(s)',
|
||||
items: { type: 'string' },
|
||||
},
|
||||
internalLinks: { type: 'number', description: 'Number of internal outgoing links' },
|
||||
externalLinks: { type: 'number', description: 'Number of external outgoing links' },
|
||||
backlinks: {
|
||||
type: 'number',
|
||||
description: 'Number of incoming external links to the page',
|
||||
optional: true,
|
||||
},
|
||||
compliant: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the page is indexable (200 status, no canonical/noindex)',
|
||||
optional: true,
|
||||
},
|
||||
traffic: {
|
||||
type: 'number',
|
||||
description: 'Estimated monthly organic traffic to the page',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import type { AhrefsTopPagesParams, AhrefsTopPagesResponse } from '@/tools/ahrefs/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const SELECT_FIELDS = 'url,sum_traffic,keywords,top_keyword,value'
|
||||
|
||||
export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesResponse> = {
|
||||
id: 'ahrefs_top_pages',
|
||||
name: 'Ahrefs Top Pages',
|
||||
description:
|
||||
'Get the top pages of a target domain sorted by organic traffic. Returns page URLs with their traffic, keyword counts, and estimated traffic value.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
target: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The target domain to analyze. Example: "example.com"',
|
||||
},
|
||||
country: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")',
|
||||
},
|
||||
mode: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'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 to report metrics on, 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: 1000)',
|
||||
},
|
||||
apiKey: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Ahrefs API Key',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const url = new URL('https://api.ahrefs.com/v3/site-explorer/top-pages')
|
||||
url.searchParams.set('target', params.target)
|
||||
url.searchParams.set('country', params.country || 'us')
|
||||
url.searchParams.set('select', SELECT_FIELDS)
|
||||
// Date is required - default to today if not provided
|
||||
const date = params.date || new Date().toISOString().split('T')[0]
|
||||
url.searchParams.set('date', date)
|
||||
if (params.mode) url.searchParams.set('mode', params.mode)
|
||||
if (params.limit) url.searchParams.set('limit', String(params.limit))
|
||||
return url.toString()
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => ({
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${params.apiKey}`,
|
||||
}),
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const data = await response.json()
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || data.error || 'Failed to get top pages')
|
||||
}
|
||||
|
||||
const pages = (data.pages || []).map((page: any) => ({
|
||||
url: page.url ?? null,
|
||||
traffic: page.sum_traffic ?? 0,
|
||||
keywords: page.keywords ?? null,
|
||||
topKeyword: page.top_keyword ?? null,
|
||||
value: typeof page.value === 'number' ? page.value / 100 : null,
|
||||
}))
|
||||
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
pages,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
outputs: {
|
||||
pages: {
|
||||
type: 'array',
|
||||
description: 'List of top pages by organic traffic',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'The page URL', optional: true },
|
||||
traffic: { type: 'number', description: 'Estimated monthly organic traffic' },
|
||||
keywords: {
|
||||
type: 'number',
|
||||
description: 'Number of keywords the page ranks for',
|
||||
optional: true,
|
||||
},
|
||||
topKeyword: {
|
||||
type: 'string',
|
||||
description: 'The top keyword driving traffic to this page',
|
||||
optional: true,
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Estimated traffic value in USD',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,609 @@
|
||||
// Common types for Ahrefs API tools
|
||||
import type { ToolResponse } from '@/tools/types'
|
||||
|
||||
// Common parameters for all Ahrefs tools
|
||||
interface AhrefsBaseParams {
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
// Target mode for analysis
|
||||
export type AhrefsTargetMode = 'domain' | 'prefix' | 'subdomains' | 'exact'
|
||||
|
||||
// Historical scope for backlink-profile endpoints (no `date` param on these endpoints)
|
||||
export type AhrefsHistory = 'live' | 'all_time' | string // `since:YYYY-MM-DD` is also valid
|
||||
|
||||
// Domain Rating tool types
|
||||
export interface AhrefsDomainRatingParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
export interface AhrefsDomainRatingResponse extends ToolResponse {
|
||||
output: {
|
||||
domainRating: number
|
||||
ahrefsRank: number | null
|
||||
}
|
||||
}
|
||||
|
||||
// Backlinks tool types
|
||||
export interface AhrefsBacklinksParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
history?: AhrefsHistory
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsBacklink {
|
||||
urlFrom: string
|
||||
urlTo: string
|
||||
anchor: string
|
||||
domainRatingSource: number
|
||||
isDofollow: boolean
|
||||
firstSeen: string
|
||||
lastVisited: string
|
||||
}
|
||||
|
||||
export interface AhrefsBacklinksResponse extends ToolResponse {
|
||||
output: {
|
||||
backlinks: AhrefsBacklink[]
|
||||
}
|
||||
}
|
||||
|
||||
// Backlinks Stats tool types
|
||||
export interface AhrefsBacklinksStatsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
interface AhrefsBacklinksStatsResult {
|
||||
liveBacklinks: number
|
||||
liveReferringDomains: number
|
||||
allTimeBacklinks: number
|
||||
allTimeReferringDomains: number
|
||||
}
|
||||
|
||||
export interface AhrefsBacklinksStatsResponse extends ToolResponse {
|
||||
output: {
|
||||
stats: AhrefsBacklinksStatsResult
|
||||
}
|
||||
}
|
||||
|
||||
// Referring Domains tool types
|
||||
export interface AhrefsReferringDomainsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
history?: AhrefsHistory
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsReferringDomain {
|
||||
domain: string
|
||||
domainRating: number
|
||||
backlinks: number
|
||||
dofollowBacklinks: number
|
||||
firstSeen: string
|
||||
lastVisited: string | null
|
||||
}
|
||||
|
||||
export interface AhrefsReferringDomainsResponse extends ToolResponse {
|
||||
output: {
|
||||
referringDomains: AhrefsReferringDomain[]
|
||||
}
|
||||
}
|
||||
|
||||
// Organic Keywords tool types
|
||||
export interface AhrefsOrganicKeywordsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsOrganicKeyword {
|
||||
keyword: string
|
||||
volume: number
|
||||
position: number | null
|
||||
url: string | null
|
||||
traffic: number
|
||||
keywordDifficulty: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsOrganicKeywordsResponse extends ToolResponse {
|
||||
output: {
|
||||
keywords: AhrefsOrganicKeyword[]
|
||||
}
|
||||
}
|
||||
|
||||
// Top Pages tool types
|
||||
export interface AhrefsTopPagesParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsTopPage {
|
||||
url: string | null
|
||||
traffic: number
|
||||
keywords: number | null
|
||||
topKeyword: string | null
|
||||
value: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsTopPagesResponse extends ToolResponse {
|
||||
output: {
|
||||
pages: AhrefsTopPage[]
|
||||
}
|
||||
}
|
||||
|
||||
// Keyword Overview tool types
|
||||
export interface AhrefsKeywordOverviewParams extends AhrefsBaseParams {
|
||||
keyword: string
|
||||
country?: string
|
||||
}
|
||||
|
||||
interface AhrefsKeywordIntents {
|
||||
informational: boolean
|
||||
navigational: boolean
|
||||
commercial: boolean
|
||||
transactional: boolean
|
||||
branded: boolean
|
||||
local: boolean
|
||||
}
|
||||
|
||||
interface AhrefsKeywordOverviewResult {
|
||||
keyword: string
|
||||
searchVolume: number
|
||||
keywordDifficulty: number | null
|
||||
cpc: number | null
|
||||
clicks: number | null
|
||||
clicksPercentage: number | null
|
||||
parentTopic: string | null
|
||||
trafficPotential: number | null
|
||||
intents: AhrefsKeywordIntents | null
|
||||
}
|
||||
|
||||
export interface AhrefsKeywordOverviewResponse extends ToolResponse {
|
||||
output: {
|
||||
overview: AhrefsKeywordOverviewResult
|
||||
}
|
||||
}
|
||||
|
||||
// Broken Backlinks tool types
|
||||
export interface AhrefsBrokenBacklinksParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsBrokenBacklink {
|
||||
urlFrom: string
|
||||
urlTo: string
|
||||
httpCode: number | null
|
||||
anchor: string
|
||||
domainRatingSource: number
|
||||
}
|
||||
|
||||
export interface AhrefsBrokenBacklinksResponse extends ToolResponse {
|
||||
output: {
|
||||
brokenBacklinks: AhrefsBrokenBacklink[]
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics tool types (single-call organic + paid search overview)
|
||||
export interface AhrefsMetricsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
}
|
||||
|
||||
interface AhrefsMetricsResult {
|
||||
organicTraffic: number
|
||||
organicKeywords: number
|
||||
organicKeywordsTop3: number
|
||||
organicCost: number | null
|
||||
paidTraffic: number
|
||||
paidKeywords: number
|
||||
paidPages: number
|
||||
paidCost: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsMetricsResponse extends ToolResponse {
|
||||
output: {
|
||||
metrics: AhrefsMetricsResult
|
||||
}
|
||||
}
|
||||
|
||||
// Organic Competitors tool types
|
||||
export interface AhrefsOrganicCompetitorsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsOrganicCompetitor {
|
||||
domain: string | null
|
||||
domainRating: number
|
||||
commonKeywords: number
|
||||
targetKeywords: number
|
||||
competitorKeywords: number
|
||||
traffic: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsOrganicCompetitorsResponse extends ToolResponse {
|
||||
output: {
|
||||
competitors: AhrefsOrganicCompetitor[]
|
||||
}
|
||||
}
|
||||
|
||||
// Rank Tracker device type
|
||||
export type AhrefsRankTrackerDevice = 'desktop' | 'mobile'
|
||||
|
||||
// Rank Tracker search volume calculation mode
|
||||
export type AhrefsVolumeMode = 'monthly' | 'average'
|
||||
|
||||
// Rank Tracker Overview tool types
|
||||
export interface AhrefsRankTrackerOverviewParams extends AhrefsBaseParams {
|
||||
projectId: number
|
||||
date: string // Date in YYYY-MM-DD format (required by the API)
|
||||
device: AhrefsRankTrackerDevice
|
||||
dateCompared?: string
|
||||
volumeMode?: AhrefsVolumeMode
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsRankTrackerOverviewItem {
|
||||
keyword: string
|
||||
position: number | null
|
||||
volume: number | null
|
||||
keywordDifficulty: number | null
|
||||
url: string | null
|
||||
traffic: number | null
|
||||
serpFeatures: string[]
|
||||
bestPositionKind: string | null
|
||||
}
|
||||
|
||||
export interface AhrefsRankTrackerOverviewResponse extends ToolResponse {
|
||||
output: {
|
||||
overviews: AhrefsRankTrackerOverviewItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Rank Tracker SERP Overview tool types
|
||||
export interface AhrefsRankTrackerSerpOverviewParams extends AhrefsBaseParams {
|
||||
projectId: number
|
||||
keyword: string
|
||||
country: string
|
||||
device: AhrefsRankTrackerDevice
|
||||
topPositions?: number
|
||||
date?: string // ISO date-time (YYYY-MM-DDThh:mm:ss)
|
||||
locationId?: number
|
||||
languageCode?: string
|
||||
}
|
||||
|
||||
interface AhrefsSerpPosition {
|
||||
position: number
|
||||
url: string
|
||||
title: string
|
||||
type: string[]
|
||||
domainRating: number
|
||||
urlRating: number
|
||||
backlinks: number
|
||||
refdomains: number
|
||||
traffic: number
|
||||
value: number | null
|
||||
topKeyword: string | null
|
||||
topKeywordVolume: number | null
|
||||
updateDate: string
|
||||
}
|
||||
|
||||
export interface AhrefsRankTrackerSerpOverviewResponse extends ToolResponse {
|
||||
output: {
|
||||
positions: AhrefsSerpPosition[]
|
||||
}
|
||||
}
|
||||
|
||||
// Rank Tracker Competitors Overview tool types
|
||||
export interface AhrefsRankTrackerCompetitorsOverviewParams extends AhrefsBaseParams {
|
||||
projectId: number
|
||||
date: string
|
||||
device: AhrefsRankTrackerDevice
|
||||
dateCompared?: string
|
||||
volumeMode?: AhrefsVolumeMode
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsCompetitorListItem {
|
||||
url: string
|
||||
position: number | null
|
||||
bestPositionKind: string | null
|
||||
traffic: number | null
|
||||
value: number | null
|
||||
}
|
||||
|
||||
interface AhrefsRankTrackerCompetitorsOverviewItem {
|
||||
keyword: string
|
||||
volume: number | null
|
||||
keywordDifficulty: number | null
|
||||
serpFeatures: string[]
|
||||
competitorsList: AhrefsCompetitorListItem[]
|
||||
}
|
||||
|
||||
export interface AhrefsRankTrackerCompetitorsOverviewResponse extends ToolResponse {
|
||||
output: {
|
||||
competitorKeywords: AhrefsRankTrackerCompetitorsOverviewItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Rank Tracker Competitors Stats tool types
|
||||
export interface AhrefsRankTrackerCompetitorsStatsParams extends AhrefsBaseParams {
|
||||
projectId: number
|
||||
date: string
|
||||
device: AhrefsRankTrackerDevice
|
||||
volumeMode?: AhrefsVolumeMode
|
||||
}
|
||||
|
||||
interface AhrefsCompetitorStat {
|
||||
competitor: string
|
||||
traffic: number | null
|
||||
trafficValue: number | null
|
||||
averagePosition: number | null
|
||||
pos1To3: number
|
||||
pos4To10: number
|
||||
shareOfVoice: number
|
||||
shareOfTrafficValue: number
|
||||
}
|
||||
|
||||
export interface AhrefsRankTrackerCompetitorsStatsResponse extends ToolResponse {
|
||||
output: {
|
||||
competitorsStats: AhrefsCompetitorStat[]
|
||||
}
|
||||
}
|
||||
|
||||
// Batch Analysis tool types
|
||||
export interface AhrefsBatchAnalysisParams extends AhrefsBaseParams {
|
||||
targets: string // Comma-separated list of domains/URLs
|
||||
mode?: AhrefsTargetMode
|
||||
protocol?: 'both' | 'http' | 'https'
|
||||
country?: string
|
||||
volumeMode?: AhrefsVolumeMode
|
||||
}
|
||||
|
||||
interface AhrefsBatchAnalysisResult {
|
||||
url: string
|
||||
index: number
|
||||
domainRating: number | null
|
||||
ahrefsRank: number | null
|
||||
backlinks: number | null
|
||||
referringDomains: number | null
|
||||
organicTraffic: number | null
|
||||
organicKeywords: number | null
|
||||
paidTraffic: number | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export interface AhrefsBatchAnalysisResponse extends ToolResponse {
|
||||
output: {
|
||||
results: AhrefsBatchAnalysisResult[]
|
||||
}
|
||||
}
|
||||
|
||||
// Site Audit Page Explorer tool types
|
||||
export interface AhrefsSiteAuditPageExplorerParams extends AhrefsBaseParams {
|
||||
projectId: number
|
||||
date?: string // ISO date-time (YYYY-MM-DDThh:mm:ss), defaults to most recent crawl
|
||||
limit?: number
|
||||
offset?: number
|
||||
issueId?: string
|
||||
}
|
||||
|
||||
interface AhrefsPageExplorerResult {
|
||||
url: string
|
||||
httpCode: number | null
|
||||
title: string[]
|
||||
internalLinks: number
|
||||
externalLinks: number
|
||||
backlinks: number | null
|
||||
compliant: boolean | null
|
||||
traffic: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsSiteAuditPageExplorerResponse extends ToolResponse {
|
||||
output: {
|
||||
auditPages: AhrefsPageExplorerResult[]
|
||||
}
|
||||
}
|
||||
|
||||
// Domain Rating History tool types
|
||||
export interface AhrefsDomainRatingHistoryParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
dateFrom: string
|
||||
dateTo?: string
|
||||
historyGrouping?: 'daily' | 'weekly' | 'monthly'
|
||||
}
|
||||
|
||||
interface AhrefsDomainRatingHistoryItem {
|
||||
date: string
|
||||
domainRating: number
|
||||
}
|
||||
|
||||
export interface AhrefsDomainRatingHistoryResponse extends ToolResponse {
|
||||
output: {
|
||||
domainRatings: AhrefsDomainRatingHistoryItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Metrics History tool types
|
||||
export interface AhrefsMetricsHistoryParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
dateFrom: string
|
||||
dateTo?: string
|
||||
volumeMode?: AhrefsVolumeMode
|
||||
historyGrouping?: 'daily' | 'weekly' | 'monthly'
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
}
|
||||
|
||||
interface AhrefsMetricsHistoryItem {
|
||||
date: string
|
||||
organicTraffic: number
|
||||
organicCost: number | null
|
||||
paidTraffic: number
|
||||
paidCost: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsMetricsHistoryResponse extends ToolResponse {
|
||||
output: {
|
||||
metricsHistory: AhrefsMetricsHistoryItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Referring Domains History tool types
|
||||
export interface AhrefsRefdomainsHistoryParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
dateFrom: string
|
||||
dateTo?: string
|
||||
historyGrouping?: 'daily' | 'weekly' | 'monthly'
|
||||
mode?: AhrefsTargetMode
|
||||
}
|
||||
|
||||
interface AhrefsRefdomainsHistoryItem {
|
||||
date: string
|
||||
referringDomains: number
|
||||
}
|
||||
|
||||
export interface AhrefsRefdomainsHistoryResponse extends ToolResponse {
|
||||
output: {
|
||||
referringDomainsHistory: AhrefsRefdomainsHistoryItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Keywords History tool types
|
||||
export interface AhrefsKeywordsHistoryParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
dateFrom: string
|
||||
dateTo?: string
|
||||
historyGrouping?: 'daily' | 'weekly' | 'monthly'
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
}
|
||||
|
||||
interface AhrefsKeywordsHistoryItem {
|
||||
date: string
|
||||
top3: number
|
||||
top4To10: number
|
||||
top11To20: number
|
||||
top21To50: number
|
||||
top51Plus: number
|
||||
}
|
||||
|
||||
export interface AhrefsKeywordsHistoryResponse extends ToolResponse {
|
||||
output: {
|
||||
keywordsHistory: AhrefsKeywordsHistoryItem[]
|
||||
}
|
||||
}
|
||||
|
||||
// Related Terms tool types
|
||||
export interface AhrefsRelatedTermsParams extends AhrefsBaseParams {
|
||||
keyword: string
|
||||
country?: string
|
||||
terms?: 'also_rank_for' | 'also_talk_about' | 'all'
|
||||
viewFor?: 'top_10' | 'top_100'
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsRelatedTerm {
|
||||
keyword: string
|
||||
volume: number | null
|
||||
keywordDifficulty: number | null
|
||||
cpc: number | null
|
||||
parentTopic: string | null
|
||||
trafficPotential: number | null
|
||||
intents: Record<string, boolean> | null
|
||||
serpFeatures: string[]
|
||||
}
|
||||
|
||||
export interface AhrefsRelatedTermsResponse extends ToolResponse {
|
||||
output: {
|
||||
relatedTerms: AhrefsRelatedTerm[]
|
||||
}
|
||||
}
|
||||
|
||||
// Anchors tool types
|
||||
export interface AhrefsAnchorsParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
mode?: AhrefsTargetMode
|
||||
history?: AhrefsHistory
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsAnchor {
|
||||
anchor: string
|
||||
backlinks: number
|
||||
dofollowBacklinks: number
|
||||
referringDomains: number
|
||||
firstSeen: string
|
||||
lastSeen: string | null
|
||||
}
|
||||
|
||||
export interface AhrefsAnchorsResponse extends ToolResponse {
|
||||
output: {
|
||||
anchors: AhrefsAnchor[]
|
||||
}
|
||||
}
|
||||
|
||||
// Paid Pages tool types
|
||||
export interface AhrefsPaidPagesParams extends AhrefsBaseParams {
|
||||
target: string
|
||||
country?: string
|
||||
mode?: AhrefsTargetMode
|
||||
date?: string // Date in YYYY-MM-DD format, defaults to today
|
||||
limit?: number
|
||||
}
|
||||
|
||||
interface AhrefsPaidPage {
|
||||
url: string | null
|
||||
traffic: number | null
|
||||
keywords: number | null
|
||||
topKeyword: string | null
|
||||
value: number | null
|
||||
adsCount: number | null
|
||||
}
|
||||
|
||||
export interface AhrefsPaidPagesResponse extends ToolResponse {
|
||||
output: {
|
||||
paidPages: AhrefsPaidPage[]
|
||||
}
|
||||
}
|
||||
|
||||
// Union type for all possible responses
|
||||
export type AhrefsResponse =
|
||||
| AhrefsDomainRatingResponse
|
||||
| AhrefsBacklinksResponse
|
||||
| AhrefsBacklinksStatsResponse
|
||||
| AhrefsReferringDomainsResponse
|
||||
| AhrefsOrganicKeywordsResponse
|
||||
| AhrefsTopPagesResponse
|
||||
| AhrefsKeywordOverviewResponse
|
||||
| AhrefsBrokenBacklinksResponse
|
||||
| AhrefsMetricsResponse
|
||||
| AhrefsOrganicCompetitorsResponse
|
||||
| AhrefsRankTrackerOverviewResponse
|
||||
| AhrefsRankTrackerSerpOverviewResponse
|
||||
| AhrefsRankTrackerCompetitorsOverviewResponse
|
||||
| AhrefsRankTrackerCompetitorsStatsResponse
|
||||
| AhrefsBatchAnalysisResponse
|
||||
| AhrefsSiteAuditPageExplorerResponse
|
||||
| AhrefsDomainRatingHistoryResponse
|
||||
| AhrefsMetricsHistoryResponse
|
||||
| AhrefsRefdomainsHistoryResponse
|
||||
| AhrefsKeywordsHistoryResponse
|
||||
| AhrefsRelatedTermsResponse
|
||||
| AhrefsAnchorsResponse
|
||||
| AhrefsPaidPagesResponse
|
||||
Reference in New Issue
Block a user