fix(ahrefs): align tool coverage and outputs with the Ahrefs API v3 (#5367)

* fix(ahrefs): align tool coverage and outputs with the Ahrefs API v3

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

* fix(ahrefs): default metrics country to us like every other tool

Greptile and Cursor Bugbot both flagged that ahrefs_metrics omitted
the country when unset, unlike every other country-accepting Ahrefs
tool, which silently returns global data instead of US data on
direct tool calls.

* fix(ahrefs): default history to all_time on backlinks and referring domains

Cursor Bugbot flagged that history was only sent when explicitly set,
even though the block UI defaults it to all_time — same class of gap
as the metrics.ts country fix, applied for direct tool-call consistency.

* feat(ahrefs): expose search intent flags on keyword overview

Adds the real intents field (informational, navigational, commercial,
transactional, branded, local) from keywords-explorer/overview, which
was verified valid against the live API docs but not yet surfaced.
This commit is contained in:
Waleed
2026-07-02 09:49:24 -07:00
committed by GitHub
parent dabb856b9a
commit 02b1de4faf
14 changed files with 829 additions and 526 deletions
+305 -343
View File
@@ -3,6 +3,45 @@ import type { BlockConfig, BlockMeta } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import type { AhrefsResponse } from '@/tools/ahrefs/types'
const COUNTRY_OPTIONS = [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
]
const MODE_OPTIONS = [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
]
const DATE_WAND_CONFIG = {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp' as const,
}
export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
type: 'ahrefs',
name: 'Ahrefs',
@@ -10,7 +49,7 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
authMode: AuthMode.ApiKey,
longDescription:
'Integrate Ahrefs SEO tools into your workflow. Analyze domain ratings, backlinks, organic keywords, top pages, and more. Requires an Ahrefs Enterprise plan with API access.',
docsLink: 'https://docs.ahrefs.com/docs/api/reference/introduction',
docsLink: 'https://docs.sim.ai/integrations/ahrefs',
category: 'tools',
integrationType: IntegrationType.Analytics,
bgColor: '#FFFFFF',
@@ -22,13 +61,15 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
type: 'dropdown',
options: [
{ label: 'Domain Rating', id: 'ahrefs_domain_rating' },
{ label: 'Metrics Overview', id: 'ahrefs_metrics' },
{ label: 'Backlinks', id: 'ahrefs_backlinks' },
{ label: 'Backlinks Stats', id: 'ahrefs_backlinks_stats' },
{ label: 'Referring Domains', id: 'ahrefs_referring_domains' },
{ label: 'Broken Backlinks', id: 'ahrefs_broken_backlinks' },
{ label: 'Organic Keywords', id: 'ahrefs_organic_keywords' },
{ label: 'Organic Competitors', id: 'ahrefs_organic_competitors' },
{ label: 'Top Pages', id: 'ahrefs_top_pages' },
{ label: 'Keyword Overview', id: 'ahrefs_keyword_overview' },
{ label: 'Broken Backlinks', id: 'ahrefs_broken_backlinks' },
],
value: () => 'ahrefs_domain_rating',
},
@@ -48,19 +89,43 @@ export const AhrefsBlock: BlockConfig<AhrefsResponse> = {
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_domain_rating' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
wandConfig: DATE_WAND_CONFIG,
},
// Metrics operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_metrics' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: COUNTRY_OPTIONS,
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_metrics' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_metrics' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_metrics' },
mode: 'advanced',
wandConfig: DATE_WAND_CONFIG,
},
// Backlinks operation inputs
{
@@ -75,53 +140,31 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
mode: 'advanced',
},
{
id: 'history',
title: 'History',
type: 'dropdown',
options: [
{ label: 'All time (includes lost backlinks)', id: 'all_time' },
{ label: 'Live only', id: 'live' },
],
value: () => 'all_time',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
mode: 'advanced',
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_backlinks' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
},
// Backlinks Stats operation inputs
{
id: 'target',
@@ -135,12 +178,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
mode: 'advanced',
@@ -152,19 +190,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_backlinks_stats' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
wandConfig: DATE_WAND_CONFIG,
},
// Referring Domains operation inputs
{
@@ -179,256 +205,31 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
mode: 'advanced',
},
{
id: 'history',
title: 'History',
type: 'dropdown',
options: [
{ label: 'All time (includes lost domains)', id: 'all_time' },
{ label: 'Live only', id: 'live' },
],
value: () => 'all_time',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
mode: 'advanced',
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_referring_domains' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
},
// Organic Keywords operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
},
// Top Pages operation inputs
{
id: 'target',
title: 'Target Domain',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'offset',
title: 'Offset',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
},
// Keyword Overview operation inputs
{
id: 'keyword',
title: 'Keyword',
type: 'short-input',
placeholder: 'Enter keyword to analyze',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: [
{ label: 'United States', id: 'us' },
{ label: 'United Kingdom', id: 'gb' },
{ label: 'Germany', id: 'de' },
{ label: 'France', id: 'fr' },
{ label: 'Spain', id: 'es' },
{ label: 'Italy', id: 'it' },
{ label: 'Canada', id: 'ca' },
{ label: 'Australia', id: 'au' },
{ label: 'Japan', id: 'jp' },
{ label: 'Brazil', id: 'br' },
{ label: 'India', id: 'in' },
{ label: 'Netherlands', id: 'nl' },
{ label: 'Poland', id: 'pl' },
{ label: 'Russia', id: 'ru' },
{ label: 'Mexico', id: 'mx' },
],
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
mode: 'advanced',
},
// Broken Backlinks operation inputs
{
id: 'target',
@@ -442,12 +243,7 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
{ label: 'Exact (exact URL)', id: 'exact' },
],
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
mode: 'advanced',
@@ -456,16 +252,43 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '100',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
mode: 'advanced',
},
// Organic Keywords operation inputs
{
id: 'offset',
title: 'Offset',
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: '0',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: COUNTRY_OPTIONS,
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
},
{
@@ -473,21 +296,119 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_broken_backlinks' },
condition: { field: 'operation', value: 'ahrefs_organic_keywords' },
mode: 'advanced',
wandConfig: DATE_WAND_CONFIG,
},
// Organic Competitors operation inputs
{
id: 'target',
title: 'Target Domain/URL',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: COUNTRY_OPTIONS,
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: MODE_OPTIONS,
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_organic_competitors' },
mode: 'advanced',
wandConfig: DATE_WAND_CONFIG,
},
// Top Pages operation inputs
{
id: 'target',
title: 'Target Domain',
type: 'short-input',
placeholder: 'example.com',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: COUNTRY_OPTIONS,
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'mode',
title: 'Analysis Mode',
type: 'dropdown',
options: [
{ label: 'Domain (entire domain)', id: 'domain' },
{ label: 'Prefix (URL prefix)', id: 'prefix' },
{ label: 'Subdomains (include all)', id: 'subdomains' },
],
value: () => 'domain',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'limit',
title: 'Limit',
type: 'short-input',
placeholder: '1000',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
},
{
id: 'date',
title: 'Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD (defaults to today)',
condition: { field: 'operation', value: 'ahrefs_top_pages' },
mode: 'advanced',
wandConfig: DATE_WAND_CONFIG,
},
// Keyword Overview operation inputs
{
id: 'keyword',
title: 'Keyword',
type: 'short-input',
placeholder: 'Enter keyword to analyze',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
required: true,
},
{
id: 'country',
title: 'Country',
type: 'dropdown',
options: COUNTRY_OPTIONS,
value: () => 'us',
condition: { field: 'operation', value: 'ahrefs_keyword_overview' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a date in YYYY-MM-DD format based on the user's description.
Examples:
- "today" -> Current date in YYYY-MM-DD format
- "yesterday" -> Yesterday's date in YYYY-MM-DD format
- "last week" -> Date 7 days ago in YYYY-MM-DD format
- "beginning of this month" -> First day of current month in YYYY-MM-DD format
Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, no extra text.`,
placeholder: 'Describe the date (e.g., "yesterday", "last week", "start of month")...',
generationType: 'timestamp',
},
},
// API Key (common to all operations)
{
@@ -502,33 +423,39 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
tools: {
access: [
'ahrefs_domain_rating',
'ahrefs_metrics',
'ahrefs_backlinks',
'ahrefs_backlinks_stats',
'ahrefs_referring_domains',
'ahrefs_broken_backlinks',
'ahrefs_organic_keywords',
'ahrefs_organic_competitors',
'ahrefs_top_pages',
'ahrefs_keyword_overview',
'ahrefs_broken_backlinks',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'ahrefs_domain_rating':
return 'ahrefs_domain_rating'
case 'ahrefs_metrics':
return 'ahrefs_metrics'
case 'ahrefs_backlinks':
return 'ahrefs_backlinks'
case 'ahrefs_backlinks_stats':
return 'ahrefs_backlinks_stats'
case 'ahrefs_referring_domains':
return 'ahrefs_referring_domains'
case 'ahrefs_broken_backlinks':
return 'ahrefs_broken_backlinks'
case 'ahrefs_organic_keywords':
return 'ahrefs_organic_keywords'
case 'ahrefs_organic_competitors':
return 'ahrefs_organic_competitors'
case 'ahrefs_top_pages':
return 'ahrefs_top_pages'
case 'ahrefs_keyword_overview':
return 'ahrefs_keyword_overview'
case 'ahrefs_broken_backlinks':
return 'ahrefs_broken_backlinks'
default:
return 'ahrefs_domain_rating'
}
@@ -536,7 +463,6 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
params: (params) => {
const result: Record<string, unknown> = {}
if (params.limit) result.limit = Number(params.limit)
if (params.offset) result.offset = Number(params.offset)
return result
},
},
@@ -549,27 +475,46 @@ Return ONLY the date string in YYYY-MM-DD format - no explanations, no quotes, n
mode: { type: 'string', description: 'Analysis mode (domain, prefix, subdomains, exact)' },
country: { type: 'string', description: 'Country code for geo-specific data' },
date: { type: 'string', description: 'Date for historical data in YYYY-MM-DD format' },
history: {
type: 'string',
description: 'Historical scope for backlink-profile endpoints (all_time, live)',
},
limit: { type: 'number', description: 'Maximum number of results to return' },
offset: { type: 'number', description: 'Number of results to skip for pagination' },
},
outputs: {
// Domain Rating output
domainRating: { type: 'number', description: 'Domain Rating score (0-100)' },
ahrefsRank: { type: 'number', description: 'Ahrefs Rank (global ranking)' },
// Metrics output
metrics: {
type: 'json',
description:
'Organic and paid search overview (organicTraffic, organicKeywords, organicKeywordsTop3, organicCost, paidTraffic, paidKeywords, paidPages, paidCost)',
},
// Backlinks output
backlinks: { type: 'json', description: 'List of backlinks' },
// Backlinks Stats output
stats: { type: 'json', description: 'Backlink statistics' },
stats: {
type: 'json',
description:
'Backlink and referring domain totals (liveBacklinks, liveReferringDomains, allTimeBacklinks, allTimeReferringDomains)',
},
// Referring Domains output
referringDomains: { type: 'json', description: 'List of referring domains' },
// Broken Backlinks output
brokenBacklinks: { type: 'json', description: 'List of broken backlinks' },
// Organic Keywords output
keywords: { type: 'json', description: 'List of organic keywords' },
// Organic Competitors output
competitors: { type: 'json', description: 'List of organic search competitors' },
// Top Pages output
pages: { type: 'json', description: 'List of top pages' },
// Keyword Overview output
overview: { type: 'json', description: 'Keyword metrics overview' },
// Broken Backlinks output
brokenBacklinks: { type: 'json', description: 'List of broken backlinks' },
overview: {
type: 'json',
description:
'Keyword metrics overview, including search intent flags (informational, navigational, commercial, transactional, branded, local)',
},
},
}
@@ -615,6 +560,16 @@ export const AhrefsBlockMeta = {
category: 'marketing',
tags: ['marketing', 'automation'],
},
{
icon: AhrefsIcon,
title: 'Ahrefs organic competitor finder',
prompt:
'Build a monthly workflow that pulls Ahrefs organic competitors for my domain, cross-references them against my tracked competitor list, and posts newly surfaced competitors to Slack for review.',
modules: ['scheduled', 'agent', 'workflows'],
category: 'marketing',
tags: ['marketing', 'research'],
alsoIntegrations: ['slack'],
},
{
icon: AhrefsIcon,
title: 'Ahrefs + Similarweb growth scoreboard',
@@ -668,5 +623,12 @@ export const AhrefsBlockMeta = {
content:
'# Track Organic Rankings\n\nReport how a domain is ranking in organic search using Ahrefs.\n\n## Steps\n1. Pull the organic keywords report for the target domain.\n2. Identify the top-ranking keywords and their positions.\n3. Compare against a prior snapshot if available to find gains and losses.\n\n## Output\nA summary of top organic keywords, notable position gains and drops, and pages that may need attention.',
},
{
name: 'find-organic-competitors',
description:
'Use Ahrefs organic competitors data to identify sites competing for the same search traffic.',
content:
'# Find Organic Competitors\n\nSurface who actually competes with a domain in organic search using Ahrefs.\n\n## Steps\n1. Run an organic competitors report for the target domain.\n2. Rank results by common keyword overlap and competitor traffic.\n3. Cross-reference against the known competitor list to flag new entrants.\n\n## Output\nA ranked list of organic competitors with keyword overlap and traffic, highlighting any that are not yet being tracked.',
},
],
} as const satisfies BlockMeta
+14 -18
View File
@@ -1,6 +1,9 @@
import type { AhrefsBacklinksParams, AhrefsBacklinksResponse } from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
const SELECT_FIELDS =
'url_from,url_to,anchor,domain_rating_source,is_dofollow,first_seen,last_visited'
export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksResponse> = {
id: 'ahrefs_backlinks',
name: 'Ahrefs Backlinks',
@@ -21,25 +24,20 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
history: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
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: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
@@ -51,14 +49,12 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/backlinks')
const url = new URL('https://api.ahrefs.com/v3/site-explorer/all-backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
url.searchParams.set('select', SELECT_FIELDS)
if (params.mode) url.searchParams.set('mode', params.mode)
url.searchParams.set('history', params.history || 'all_time')
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
@@ -79,8 +75,8 @@ export const backlinksTool: ToolConfig<AhrefsBacklinksParams, AhrefsBacklinksRes
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
isDofollow: link.is_dofollow ?? link.dofollow ?? false,
domainRatingSource: link.domain_rating_source ?? 0,
isDofollow: link.is_dofollow ?? false,
firstSeen: link.first_seen || '',
lastVisited: link.last_visited || '',
}))
+23 -16
View File
@@ -8,7 +8,7 @@ export const backlinksStatsTool: ToolConfig<
id: 'ahrefs_backlinks_stats',
name: 'Ahrefs Backlinks Stats',
description:
'Get backlink statistics for a target domain or URL. Returns totals for different backlink types including dofollow, nofollow, text, image, and redirect links.',
'Get backlink and referring domain totals for a target domain or URL, both currently live and across all time.',
version: '1.0.0',
params: {
@@ -24,13 +24,13 @@ export const backlinksStatsTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
},
apiKey: {
type: 'string',
@@ -64,16 +64,16 @@ export const backlinksStatsTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get backlinks stats')
}
const metrics = data.metrics || {}
return {
success: true,
output: {
stats: {
total: data.live ?? data.total ?? 0,
dofollow: data.live_dofollow ?? data.dofollow ?? 0,
nofollow: data.live_nofollow ?? data.nofollow ?? 0,
text: data.text ?? 0,
image: data.image ?? 0,
redirect: data.redirect ?? 0,
liveBacklinks: metrics.live ?? 0,
liveReferringDomains: metrics.live_refdomains ?? 0,
allTimeBacklinks: metrics.all_time ?? 0,
allTimeReferringDomains: metrics.all_time_refdomains ?? 0,
},
},
}
@@ -82,14 +82,21 @@ export const backlinksStatsTool: ToolConfig<
outputs: {
stats: {
type: 'object',
description: 'Backlink statistics summary',
description: 'Backlink and referring domain totals',
properties: {
total: { type: 'number', description: 'Total number of live backlinks' },
dofollow: { type: 'number', description: 'Number of dofollow backlinks' },
nofollow: { type: 'number', description: 'Number of nofollow backlinks' },
text: { type: 'number', description: 'Number of text backlinks' },
image: { type: 'number', description: 'Number of image backlinks' },
redirect: { type: 'number', description: 'Number of redirect backlinks' },
liveBacklinks: { type: 'number', description: 'Number of currently live backlinks' },
liveReferringDomains: {
type: 'number',
description: 'Number of currently live referring domains',
},
allTimeBacklinks: {
type: 'number',
description: 'Total backlinks ever discovered, including lost ones',
},
allTimeReferringDomains: {
type: 'number',
description: 'Total referring domains ever discovered, including lost ones',
},
},
},
},
+13 -22
View File
@@ -4,6 +4,8 @@ import type {
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
const SELECT_FIELDS = 'url_from,url_to,http_code_target,anchor,domain_rating_source'
export const brokenBacklinksTool: ToolConfig<
AhrefsBrokenBacklinksParams,
AhrefsBrokenBacklinksResponse
@@ -27,25 +29,13 @@ export const brokenBacklinksTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return. Example: 50 (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
@@ -59,12 +49,9 @@ export const brokenBacklinksTool: ToolConfig<
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/broken-backlinks')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
url.searchParams.set('select', SELECT_FIELDS)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
@@ -81,12 +68,12 @@ export const brokenBacklinksTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get broken backlinks')
}
const brokenBacklinks = (data.backlinks || data.broken_backlinks || []).map((link: any) => ({
const brokenBacklinks = (data.backlinks || []).map((link: any) => ({
urlFrom: link.url_from || '',
urlTo: link.url_to || '',
httpCode: link.http_code ?? link.status_code ?? 404,
httpCode: link.http_code_target ?? null,
anchor: link.anchor || '',
domainRatingSource: link.domain_rating_source ?? link.domain_rating ?? 0,
domainRatingSource: link.domain_rating_source ?? 0,
}))
return {
@@ -109,7 +96,11 @@ export const brokenBacklinksTool: ToolConfig<
description: 'The URL of the page containing the broken link',
},
urlTo: { type: 'string', description: 'The broken URL being linked to' },
httpCode: { type: 'number', description: 'HTTP status code (e.g., 404, 410)' },
httpCode: {
type: 'number',
description: 'HTTP status code of the broken target URL (e.g., 404, 410)',
optional: true,
},
anchor: { type: 'string', description: 'The anchor text of the link' },
domainRatingSource: {
type: 'number',
+3 -2
View File
@@ -55,8 +55,8 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
return {
success: true,
output: {
domainRating: data.domain_rating ?? 0,
ahrefsRank: data.ahrefs_rank ?? 0,
domainRating: data.domain_rating?.domain_rating ?? 0,
ahrefsRank: data.domain_rating?.ahrefs_rank ?? null,
},
}
},
@@ -69,6 +69,7 @@ export const domainRatingTool: ToolConfig<AhrefsDomainRatingParams, AhrefsDomain
ahrefsRank: {
type: 'number',
description: 'Ahrefs Rank - global ranking based on backlink profile strength',
optional: true,
},
},
}
+6
View File
@@ -3,6 +3,8 @@ import { backlinksStatsTool } from '@/tools/ahrefs/backlinks_stats'
import { brokenBacklinksTool } from '@/tools/ahrefs/broken_backlinks'
import { domainRatingTool } from '@/tools/ahrefs/domain_rating'
import { keywordOverviewTool } from '@/tools/ahrefs/keyword_overview'
import { metricsTool } from '@/tools/ahrefs/metrics'
import { organicCompetitorsTool } from '@/tools/ahrefs/organic_competitors'
import { organicKeywordsTool } from '@/tools/ahrefs/organic_keywords'
import { referringDomainsTool } from '@/tools/ahrefs/referring_domains'
import { topPagesTool } from '@/tools/ahrefs/top_pages'
@@ -15,3 +17,7 @@ export const ahrefsOrganicKeywordsTool = organicKeywordsTool
export const ahrefsTopPagesTool = topPagesTool
export const ahrefsKeywordOverviewTool = keywordOverviewTool
export const ahrefsBrokenBacklinksTool = brokenBacklinksTool
export const ahrefsMetricsTool = metricsTool
export const ahrefsOrganicCompetitorsTool = organicCompetitorsTool
export * from '@/tools/ahrefs/types'
+41 -13
View File
@@ -4,6 +4,9 @@ import type {
} from '@/tools/ahrefs/types'
import type { ToolConfig } from '@/tools/types'
const SELECT_FIELDS =
'keyword,volume,difficulty,cpc,clicks,searches_pct_clicks_organic_only,parent_topic,traffic_potential,intents'
export const keywordOverviewTool: ToolConfig<
AhrefsKeywordOverviewParams,
AhrefsKeywordOverviewResponse
@@ -38,8 +41,9 @@ export const keywordOverviewTool: ToolConfig<
request: {
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/overview')
url.searchParams.set('keyword', params.keyword)
url.searchParams.set('keywords', params.keyword)
url.searchParams.set('country', params.country || 'us')
url.searchParams.set('select', SELECT_FIELDS)
return url.toString()
},
method: 'GET',
@@ -56,18 +60,21 @@ export const keywordOverviewTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get keyword overview')
}
const result = (data.keywords || [])[0] || {}
return {
success: true,
output: {
overview: {
keyword: data.keyword || '',
searchVolume: data.volume ?? 0,
keywordDifficulty: data.keyword_difficulty ?? data.difficulty ?? 0,
cpc: data.cpc ?? 0,
clicks: data.clicks ?? 0,
clicksPercentage: data.clicks_percentage ?? 0,
parentTopic: data.parent_topic || '',
trafficPotential: data.traffic_potential ?? 0,
keyword: result.keyword || '',
searchVolume: result.volume ?? 0,
keywordDifficulty: result.difficulty ?? null,
cpc: result.cpc ?? null,
clicks: result.clicks ?? null,
clicksPercentage: result.searches_pct_clicks_organic_only ?? null,
parentTopic: result.parent_topic ?? null,
trafficPotential: result.traffic_potential ?? null,
intents: result.intents ?? null,
},
},
}
@@ -83,17 +90,38 @@ export const keywordOverviewTool: ToolConfig<
keywordDifficulty: {
type: 'number',
description: 'Keyword difficulty score (0-100)',
optional: true,
},
cpc: { type: 'number', description: 'Cost per click in USD' },
clicks: { type: 'number', description: 'Estimated clicks per month' },
cpc: { type: 'number', description: 'Cost per click in USD', optional: true },
clicks: { type: 'number', description: 'Estimated clicks per month', optional: true },
clicksPercentage: {
type: 'number',
description: 'Percentage of searches that result in clicks',
description: 'Percentage of searches that result in an organic click',
optional: true,
},
parentTopic: {
type: 'string',
description: 'The parent topic for this keyword',
optional: true,
},
parentTopic: { type: 'string', description: 'The parent topic for this keyword' },
trafficPotential: {
type: 'number',
description: 'Estimated traffic potential if ranking #1',
optional: true,
},
intents: {
type: 'object',
description:
'Search intent flags (informational, navigational, commercial, transactional, branded, local)',
optional: true,
properties: {
informational: { type: 'boolean', description: 'Query seeks information' },
navigational: { type: 'boolean', description: 'Query seeks a specific site or page' },
commercial: { type: 'boolean', description: 'Query researches a purchase decision' },
transactional: { type: 'boolean', description: 'Query intends to complete a purchase' },
branded: { type: 'boolean', description: 'Query references a specific brand' },
local: { type: 'boolean', description: 'Query seeks local results' },
},
},
},
},
+116
View File
@@ -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: metrics.org_cost ?? null,
paidTraffic: metrics.paid_traffic ?? 0,
paidKeywords: metrics.paid_keywords ?? 0,
paidPages: metrics.paid_pages ?? 0,
paidCost: metrics.paid_cost ?? 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,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,
},
},
},
},
},
}
+23 -17
View File
@@ -4,6 +4,9 @@ import type {
} 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
@@ -33,25 +36,19 @@ export const organicKeywordsTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return. Example: 50 (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
@@ -66,12 +63,12 @@ export const organicKeywordsTool: ToolConfig<
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))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
@@ -88,13 +85,13 @@ export const organicKeywordsTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get organic keywords')
}
const keywords = (data.keywords || data.organic_keywords || []).map((kw: any) => ({
const keywords = (data.keywords || []).map((kw: any) => ({
keyword: kw.keyword || '',
volume: kw.volume ?? 0,
position: kw.position ?? 0,
url: kw.url || '',
traffic: kw.traffic ?? 0,
keywordDifficulty: kw.keyword_difficulty ?? kw.difficulty ?? 0,
position: kw.best_position ?? null,
url: kw.best_position_url ?? null,
traffic: kw.sum_traffic ?? 0,
keywordDifficulty: kw.keyword_difficulty ?? null,
}))
return {
@@ -114,12 +111,21 @@ export const organicKeywordsTool: ToolConfig<
properties: {
keyword: { type: 'string', description: 'The keyword' },
volume: { type: 'number', description: 'Monthly search volume' },
position: { type: 'number', description: 'Current ranking position' },
url: { type: 'string', description: 'The URL that ranks for this keyword' },
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,
},
},
},
+24 -27
View File
@@ -4,6 +4,8 @@ import type {
} 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
@@ -27,25 +29,20 @@ export const referringDomainsTool: ToolConfig<
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains), exact (exact URL match). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match). Example: "domain"',
},
date: {
history: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
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: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
@@ -59,12 +56,10 @@ export const referringDomainsTool: ToolConfig<
url: (params) => {
const url = new URL('https://api.ahrefs.com/v3/site-explorer/refdomains')
url.searchParams.set('target', params.target)
// Date is required - default to today if not provided
const date = params.date || new Date().toISOString().split('T')[0]
url.searchParams.set('date', date)
url.searchParams.set('select', SELECT_FIELDS)
if (params.mode) url.searchParams.set('mode', params.mode)
url.searchParams.set('history', params.history || 'all_time')
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
@@ -81,16 +76,14 @@ export const referringDomainsTool: ToolConfig<
throw new Error(data.error?.message || data.error || 'Failed to get referring domains')
}
const referringDomains = (data.refdomains || data.referring_domains || []).map(
(domain: any) => ({
domain: domain.domain || domain.refdomain || '',
domainRating: domain.domain_rating ?? 0,
backlinks: domain.backlinks ?? 0,
dofollowBacklinks: domain.dofollow_backlinks ?? domain.dofollow ?? 0,
firstSeen: domain.first_seen || '',
lastVisited: domain.last_visited || '',
})
)
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,
@@ -111,14 +104,18 @@ export const referringDomainsTool: ToolConfig<
domainRating: { type: 'number', description: 'Domain Rating of the referring domain' },
backlinks: {
type: 'number',
description: 'Total number of backlinks from this domain',
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 checked' },
lastVisited: {
type: 'string',
description: 'When the domain was last seen linking (null if never re-crawled)',
optional: true,
},
},
},
},
+24 -29
View File
@@ -1,6 +1,8 @@
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',
@@ -26,32 +28,19 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
required: false,
visibility: 'user-or-llm',
description:
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains). Example: "domain"',
'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default). Example: "domain"',
},
date: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Date for historical data in YYYY-MM-DD format (defaults to today)',
description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of results to return. Example: 50 (default: 100)',
},
offset: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Number of results to skip for pagination. Example: 100',
},
select: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Comma-separated list of fields to return (e.g., url,traffic,keywords,top_keyword,value). Default: url,traffic,keywords,top_keyword,value',
description: 'Maximum number of results to return. Example: 50 (default: 1000)',
},
apiKey: {
type: 'string',
@@ -66,15 +55,12 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
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)
// Select is required by API v3 - default to common fields if not provided
const select = params.select || 'url,traffic,keywords,top_keyword,value'
url.searchParams.set('select', select)
if (params.mode) url.searchParams.set('mode', params.mode)
if (params.limit) url.searchParams.set('limit', String(params.limit))
if (params.offset) url.searchParams.set('offset', String(params.offset))
return url.toString()
},
method: 'GET',
@@ -91,12 +77,12 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
throw new Error(data.error?.message || data.error || 'Failed to get top pages')
}
const pages = (data.pages || data.top_pages || []).map((page: any) => ({
url: page.url || '',
traffic: page.traffic ?? 0,
keywords: page.keywords ?? page.keyword_count ?? 0,
topKeyword: page.top_keyword || '',
value: page.value ?? page.traffic_value ?? 0,
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: page.value ?? null,
}))
return {
@@ -114,14 +100,23 @@ export const topPagesTool: ToolConfig<AhrefsTopPagesParams, AhrefsTopPagesRespon
items: {
type: 'object',
properties: {
url: { type: 'string', description: 'The page URL' },
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' },
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,
},
value: { type: 'number', description: 'Estimated traffic value in USD' },
},
},
},
+90 -34
View File
@@ -4,26 +4,24 @@ import type { ToolResponse } from '@/tools/types'
// Common parameters for all Ahrefs tools
interface AhrefsBaseParams {
apiKey: string
date?: string // Date in YYYY-MM-DD format, defaults to today
}
// 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
}
interface AhrefsDomainRatingResult {
domain_rating: number
ahrefs_rank: number
date?: string // Date in YYYY-MM-DD format, defaults to today
}
export interface AhrefsDomainRatingResponse extends ToolResponse {
output: {
domainRating: number
ahrefsRank: number
ahrefsRank: number | null
}
}
@@ -31,8 +29,8 @@ export interface AhrefsDomainRatingResponse extends ToolResponse {
export interface AhrefsBacklinksParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
history?: AhrefsHistory
limit?: number
offset?: number
}
interface AhrefsBacklink {
@@ -55,15 +53,14 @@ export interface AhrefsBacklinksResponse extends ToolResponse {
export interface AhrefsBacklinksStatsParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
date?: string // Date in YYYY-MM-DD format, defaults to today
}
interface AhrefsBacklinksStatsResult {
total: number
dofollow: number
nofollow: number
text: number
image: number
redirect: number
liveBacklinks: number
liveReferringDomains: number
allTimeBacklinks: number
allTimeReferringDomains: number
}
export interface AhrefsBacklinksStatsResponse extends ToolResponse {
@@ -76,8 +73,8 @@ export interface AhrefsBacklinksStatsResponse extends ToolResponse {
export interface AhrefsReferringDomainsParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
history?: AhrefsHistory
limit?: number
offset?: number
}
interface AhrefsReferringDomain {
@@ -86,7 +83,7 @@ interface AhrefsReferringDomain {
backlinks: number
dofollowBacklinks: number
firstSeen: string
lastVisited: string
lastVisited: string | null
}
export interface AhrefsReferringDomainsResponse extends ToolResponse {
@@ -100,17 +97,17 @@ export interface AhrefsOrganicKeywordsParams extends AhrefsBaseParams {
target: string
country?: string
mode?: AhrefsTargetMode
date?: string // Date in YYYY-MM-DD format, defaults to today
limit?: number
offset?: number
}
interface AhrefsOrganicKeyword {
keyword: string
volume: number
position: number
url: string
position: number | null
url: string | null
traffic: number
keywordDifficulty: number
keywordDifficulty: number | null
}
export interface AhrefsOrganicKeywordsResponse extends ToolResponse {
@@ -124,17 +121,16 @@ export interface AhrefsTopPagesParams extends AhrefsBaseParams {
target: string
country?: string
mode?: AhrefsTargetMode
date?: string // Date in YYYY-MM-DD format, defaults to today
limit?: number
offset?: number
select?: string // Comma-separated list of fields to return (e.g., "url,traffic,keywords,top_keyword,value")
}
interface AhrefsTopPage {
url: string
url: string | null
traffic: number
keywords: number
topKeyword: string
value: number
keywords: number | null
topKeyword: string | null
value: number | null
}
export interface AhrefsTopPagesResponse extends ToolResponse {
@@ -149,15 +145,25 @@ export interface AhrefsKeywordOverviewParams extends AhrefsBaseParams {
country?: string
}
interface AhrefsKeywordIntents {
informational: boolean
navigational: boolean
commercial: boolean
transactional: boolean
branded: boolean
local: boolean
}
interface AhrefsKeywordOverviewResult {
keyword: string
searchVolume: number
keywordDifficulty: number
cpc: number
clicks: number
clicksPercentage: number
parentTopic: string
trafficPotential: 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 {
@@ -171,13 +177,12 @@ export interface AhrefsBrokenBacklinksParams extends AhrefsBaseParams {
target: string
mode?: AhrefsTargetMode
limit?: number
offset?: number
}
interface AhrefsBrokenBacklink {
urlFrom: string
urlTo: string
httpCode: number
httpCode: number | null
anchor: string
domainRatingSource: number
}
@@ -188,6 +193,55 @@ export interface AhrefsBrokenBacklinksResponse extends ToolResponse {
}
}
// 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[]
}
}
// Union type for all possible responses
export type AhrefsResponse =
| AhrefsDomainRatingResponse
@@ -198,3 +252,5 @@ export type AhrefsResponse =
| AhrefsTopPagesResponse
| AhrefsKeywordOverviewResponse
| AhrefsBrokenBacklinksResponse
| AhrefsMetricsResponse
| AhrefsOrganicCompetitorsResponse
+9 -5
View File
@@ -72,6 +72,8 @@ import {
ahrefsBrokenBacklinksTool,
ahrefsDomainRatingTool,
ahrefsKeywordOverviewTool,
ahrefsMetricsTool,
ahrefsOrganicCompetitorsTool,
ahrefsOrganicKeywordsTool,
ahrefsReferringDomainsTool,
ahrefsTopPagesTool,
@@ -6700,14 +6702,16 @@ export const tools: Record<string, ToolConfig> = {
attio_update_record: attioUpdateRecordTool,
attio_update_task: attioUpdateTaskTool,
attio_update_webhook: attioUpdateWebhookTool,
ahrefs_domain_rating: ahrefsDomainRatingTool,
ahrefs_backlinks: ahrefsBacklinksTool,
ahrefs_backlinks_stats: ahrefsBacklinksStatsTool,
ahrefs_referring_domains: ahrefsReferringDomainsTool,
ahrefs_organic_keywords: ahrefsOrganicKeywordsTool,
ahrefs_top_pages: ahrefsTopPagesTool,
ahrefs_keyword_overview: ahrefsKeywordOverviewTool,
ahrefs_broken_backlinks: ahrefsBrokenBacklinksTool,
ahrefs_domain_rating: ahrefsDomainRatingTool,
ahrefs_keyword_overview: ahrefsKeywordOverviewTool,
ahrefs_metrics: ahrefsMetricsTool,
ahrefs_organic_competitors: ahrefsOrganicCompetitorsTool,
ahrefs_organic_keywords: ahrefsOrganicKeywordsTool,
ahrefs_referring_domains: ahrefsReferringDomainsTool,
ahrefs_top_pages: ahrefsTopPagesTool,
apify_run_actor_sync: apifyRunActorSyncTool,
apify_run_actor_async: apifyRunActorAsyncTool,
apify_run_task: apifyRunTaskTool,