From 934dca7422ededd0185f3c8e9ce34fd4ba020e76 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 14:57:32 -0700 Subject: [PATCH 1/8] feat(ahrefs): validate integration, fix cents/column bugs, add 13 v3 endpoints Audited the existing Ahrefs integration against live API v3 docs and fixed real bugs: broken_backlinks selected the wrong column (http_code_target instead of http_code), and keyword_overview/metrics/metrics_history returned CPC/cost fields in USD cents without converting to USD. Also fixed the top-pages mode dropdown missing the "exact" option. Added 13 new tools covering previously-unsupported Ahrefs v3 endpoints: Rank Tracker (overview, SERP overview, competitors overview, competitors stats), Batch Analysis, Site Audit page explorer, four history/trend endpoints (domain rating, metrics, referring domains, keywords), Related Terms, Anchors, and Paid Pages. Note: the CPC/cost unit fix silently shifts existing organicCost/paidCost/cpc values by 100x for any workflow already consuming them (the old values were wrong, in cents instead of dollars). --- .../content/docs/en/integrations/ahrefs.mdx | 374 +++++++- apps/sim/blocks/blocks/ahrefs.ts | 845 +++++++++++++++++- apps/sim/lib/integrations/integrations.json | 56 +- apps/sim/tools/ahrefs/anchors.ts | 120 +++ apps/sim/tools/ahrefs/batch_analysis.ts | 165 ++++ apps/sim/tools/ahrefs/broken_backlinks.ts | 4 +- .../sim/tools/ahrefs/domain_rating_history.ts | 100 +++ apps/sim/tools/ahrefs/index.ts | 26 + apps/sim/tools/ahrefs/keyword_overview.ts | 3 +- apps/sim/tools/ahrefs/keywords_history.ts | 126 +++ apps/sim/tools/ahrefs/metrics.ts | 4 +- apps/sim/tools/ahrefs/metrics_history.ts | 136 +++ apps/sim/tools/ahrefs/paid_pages.ts | 135 +++ .../rank_tracker_competitors_overview.ts | 167 ++++ .../ahrefs/rank_tracker_competitors_stats.ts | 132 +++ .../sim/tools/ahrefs/rank_tracker_overview.ts | 149 +++ .../ahrefs/rank_tracker_serp_overview.ts | 166 ++++ apps/sim/tools/ahrefs/refdomains_history.ts | 113 +++ apps/sim/tools/ahrefs/related_terms.ts | 140 +++ .../tools/ahrefs/site_audit_page_explorer.ts | 142 +++ apps/sim/tools/ahrefs/types.ts | 353 ++++++++ apps/sim/tools/registry.ts | 26 + 22 files changed, 3468 insertions(+), 14 deletions(-) create mode 100644 apps/sim/tools/ahrefs/anchors.ts create mode 100644 apps/sim/tools/ahrefs/batch_analysis.ts create mode 100644 apps/sim/tools/ahrefs/domain_rating_history.ts create mode 100644 apps/sim/tools/ahrefs/keywords_history.ts create mode 100644 apps/sim/tools/ahrefs/metrics_history.ts create mode 100644 apps/sim/tools/ahrefs/paid_pages.ts create mode 100644 apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts create mode 100644 apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts create mode 100644 apps/sim/tools/ahrefs/rank_tracker_overview.ts create mode 100644 apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts create mode 100644 apps/sim/tools/ahrefs/refdomains_history.ts create mode 100644 apps/sim/tools/ahrefs/related_terms.ts create mode 100644 apps/sim/tools/ahrefs/site_audit_page_explorer.ts diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index 92d278e9fab..e037da245c0 100644 --- a/apps/docs/content/docs/en/integrations/ahrefs.mdx +++ b/apps/docs/content/docs/en/integrations/ahrefs.mdx @@ -18,8 +18,8 @@ With the Ahrefs integration in Sim, you can: - **Analyze Domain Rating & Authority**: Instantly check the Domain Rating (DR) and Ahrefs Rank of any website to gauge its authority. - **Fetch Backlinks**: Retrieve a list of backlinks pointing to a site or specific URL, with details like anchor text, referring page DR, and more. - **Get Backlink Statistics**: Access metrics on backlink types (dofollow, nofollow, text, image, redirect, etc.) for a domain or URL. -- **Explore Organic Keywords** *(planned)*: View keywords a domain ranks for and their positions in Google search results. -- **Discover Top Pages** *(planned)*: Identify the highest-performing pages by organic traffic and links. +- **Explore Organic Keywords**: View keywords a domain ranks for and their positions in Google search results. +- **Discover Top Pages**: Identify the highest-performing pages by organic traffic and links. These tools let your agents automate SEO research, monitor competitors, and generate reports—all as part of your workflow automations. To use the Ahrefs integration, you’ll need an Ahrefs Enterprise subscription with API access. {/* MANUAL-CONTENT-END */} @@ -293,4 +293,374 @@ Get detailed metrics for a keyword including search volume, keyword difficulty, | ↳ `branded` | boolean | Query references a specific brand | | ↳ `local` | boolean | Query seeks local results | +### `ahrefs_paid_pages` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `date` | string | No | Date to report metrics on, in YYYY-MM-DD format \(defaults to today\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `paidPages` | array | List of pages receiving paid search traffic | +| ↳ `url` | string | The page URL | +| ↳ `traffic` | number | Estimated monthly paid search traffic | +| ↳ `keywords` | number | Number of paid keywords the page ranks for | +| ↳ `topKeyword` | string | The top keyword driving paid traffic to this page | +| ↳ `value` | number | Estimated monthly paid traffic cost in USD | +| ↳ `adsCount` | number | Number of unique ads shown for this page | + +### `ahrefs_anchors` + +Get the anchor text distribution for a target domain or URL's backlinks, showing how many links and referring domains use each anchor text. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" or "https://example.com/page" | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `history` | string | No | Historical scope: "live" \(currently live\), "all_time" \(default, includes lost backlinks\), or "since:YYYY-MM-DD" \(backlinks found since a date\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `anchors` | array | Anchor text distribution for the backlink profile | +| ↳ `anchor` | string | The anchor text | +| ↳ `backlinks` | number | Total backlinks using this anchor text | +| ↳ `dofollowBacklinks` | number | Number of dofollow backlinks using this anchor text | +| ↳ `referringDomains` | number | Number of unique referring domains using this anchor text | +| ↳ `firstSeen` | string | When a link with this anchor was first found | +| ↳ `lastSeen` | string | When a backlink with this anchor was last seen \(null if still live\) | + +### `ahrefs_related_terms` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `keyword` | string | Yes | The seed keyword to find related terms for | +| `country` | string | No | Country code for keyword data. Example: "us", "gb", "de" \(default: "us"\) | +| `terms` | string | No | Type of related keywords to return: "also_rank_for", "also_talk_about", or "all" \(default: "all"\) | +| `viewFor` | string | No | Whether to derive related terms from the top 10 or top 100 ranking pages \(default: "top_10"\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `relatedTerms` | array | Related keyword ideas for the seed keyword | +| ↳ `keyword` | string | The related keyword | +| ↳ `volume` | number | Average monthly search volume | +| ↳ `keywordDifficulty` | number | Keyword difficulty score \(0-100\) | +| ↳ `cpc` | number | Cost per click in USD | +| ↳ `parentTopic` | string | The parent topic for this keyword | +| ↳ `trafficPotential` | number | Estimated traffic potential if ranking #1 | +| ↳ `intents` | object | Search intent flags \(informational, navigational, commercial, transactional, branded, local\) | +| ↳ `serpFeatures` | array | SERP features present in the results | + +### `ahrefs_domain_rating_history` + +Get the historical Domain Rating (DR) trend for a target domain or URL over a date range, grouped daily, weekly, or monthly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | +| `dateFrom` | string | Yes | Start date of the historical period, in YYYY-MM-DD format | +| `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | +| `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `domainRatings` | array | Historical Domain Rating data points | +| ↳ `date` | string | The date of the measurement | +| ↳ `domainRating` | number | Domain Rating score \(0-100\) on this date | + +### `ahrefs_metrics_history` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | +| `dateFrom` | string | Yes | Start date of the historical period, in YYYY-MM-DD format | +| `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | +| `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | +| `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `metricsHistory` | array | Historical organic and paid traffic data points | +| ↳ `date` | string | Date of the metric entry | +| ↳ `organicTraffic` | number | Estimated monthly organic visits | +| ↳ `organicCost` | number | Estimated monthly cost to replicate organic traffic via ads \(USD\) | +| ↳ `paidTraffic` | number | Estimated monthly paid search visits | +| ↳ `paidCost` | number | Estimated monthly paid search spend \(USD\) | + +### `ahrefs_refdomains_history` + +Get the historical referring domains trend for a target domain or URL over a date range, grouped daily, weekly, or monthly. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | +| `dateFrom` | string | Yes | Start date of the historical period, in YYYY-MM-DD format | +| `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | +| `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `referringDomainsHistory` | array | Historical referring domains count data points | +| ↳ `date` | string | The date of the data point | +| ↳ `referringDomains` | number | Total number of unique domains linking to the target on this date | + +### `ahrefs_keywords_history` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | +| `dateFrom` | string | Yes | Start date of the historical period, in YYYY-MM-DD format | +| `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | +| `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | +| `country` | string | No | Country code for search results. Example: "us", "gb", "de" | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `keywordsHistory` | array | Historical organic keyword ranking distribution | +| ↳ `date` | string | Date of the record | +| ↳ `top3` | number | Keywords ranking in top 3 organic results | +| ↳ `top4To10` | number | Keywords ranking in positions 4-10 | +| ↳ `top11To20` | number | Keywords ranking in positions 11-20 | +| ↳ `top21To50` | number | Keywords ranking in positions 21-50 | +| ↳ `top51Plus` | number | Keywords ranking in position 51 and beyond | + +### `ahrefs_batch_analysis` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `targets` | string | Yes | Comma-separated list of domains or URLs to analyze. Example: "example.com,competitor.com" | +| `mode` | string | No | Analysis mode applied to every target: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | +| `protocol` | string | No | Protocol applied to every target: "both" \(default\), "http", or "https" | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `results` | array | Bulk metrics for each analyzed target, in submission order | +| ↳ `url` | string | The analyzed target URL or domain | +| ↳ `index` | number | Index of the target in the submitted list | +| ↳ `domainRating` | number | Domain Rating score \(0-100\) | +| ↳ `ahrefsRank` | number | Ahrefs Rank \(global ranking\) | +| ↳ `backlinks` | number | Total backlinks to the target | +| ↳ `referringDomains` | number | Unique domains linking to the target | +| ↳ `organicTraffic` | number | Estimated monthly organic traffic | +| ↳ `organicKeywords` | number | Number of organic keywords ranked \(top 100\) | +| ↳ `paidTraffic` | number | Estimated monthly paid search traffic | +| ↳ `error` | string | Error message if this target could not be analyzed | + +### `ahrefs_site_audit_page_explorer` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `projectId` | number | Yes | The Site Audit project ID \(found in the project URL in Ahrefs\) | +| `date` | string | No | Crawl date in YYYY-MM-DDThh:mm:ss format \(defaults to the most recent crawl\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `offset` | number | No | Number of results to skip, for pagination | +| `issueId` | string | No | Only return pages affected by this issue ID | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `auditPages` | array | List of crawled pages with health and SEO metrics | +| ↳ `url` | string | The crawled page URL | +| ↳ `httpCode` | number | HTTP status code returned by the URL | +| ↳ `title` | array | Page title tag\(s\) | +| ↳ `internalLinks` | number | Number of internal outgoing links | +| ↳ `externalLinks` | number | Number of external outgoing links | +| ↳ `backlinks` | number | Number of incoming external links to the page | +| ↳ `compliant` | boolean | Whether the page is indexable \(200 status, no canonical/noindex\) | +| ↳ `traffic` | number | Estimated monthly organic traffic to the page | + +### `ahrefs_rank_tracker_overview` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `projectId` | number | Yes | The Rank Tracker project ID \(found in the project URL in Ahrefs\) | +| `date` | string | Yes | Date to report rankings for, in YYYY-MM-DD format | +| `device` | string | Yes | Rankings device type: "desktop" or "mobile" | +| `dateCompared` | string | No | Comparison date in YYYY-MM-DD format, to compute position/traffic deltas | +| `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `overviews` | array | Ranking overview for each tracked keyword | +| ↳ `keyword` | string | The tracked keyword | +| ↳ `position` | number | Top organic search position | +| ↳ `volume` | number | Average monthly search volume | +| ↳ `keywordDifficulty` | number | Keyword difficulty score \(0-100\) | +| ↳ `url` | string | Top-ranking URL | +| ↳ `traffic` | number | Estimated monthly organic visits | +| ↳ `serpFeatures` | array | SERP features present in the results | +| ↳ `bestPositionKind` | string | Type of the top position \(organic, paid, or SERP feature\) | + +### `ahrefs_rank_tracker_serp_overview` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `projectId` | number | Yes | The Rank Tracker project ID \(found in the project URL in Ahrefs\) | +| `keyword` | string | Yes | The tracked keyword to retrieve SERP data for | +| `country` | string | Yes | Country code for the tracked keyword. Example: "us", "gb", "de" | +| `device` | string | Yes | Rankings device type: "desktop" or "mobile" | +| `topPositions` | number | No | Number of top organic positions to return \(defaults to all available\) | +| `date` | string | No | Timestamp to return the last available SERP Overview at, in YYYY-MM-DDThh:mm:ss format | +| `locationId` | number | No | Location ID of the tracked keyword, if tracked at a specific location | +| `languageCode` | string | No | Language code of the tracked keyword | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `positions` | array | Every ranking result on the SERP for the tracked keyword | +| ↳ `position` | number | Position of the result in the SERP | +| ↳ `url` | string | URL of the ranking page | +| ↳ `title` | string | Page title | +| ↳ `type` | array | The kind of the position: organic, paid, or a SERP feature | +| ↳ `domainRating` | number | Domain Rating of the ranking domain | +| ↳ `urlRating` | number | URL Rating of the ranking page | +| ↳ `backlinks` | number | Total backlinks to the ranking domain | +| ↳ `refdomains` | number | Unique referring domains | +| ↳ `traffic` | number | Estimated monthly organic search traffic | +| ↳ `value` | number | Estimated monthly traffic value in USD cents | +| ↳ `topKeyword` | string | Highest-traffic keyword ranking for this page | +| ↳ `topKeywordVolume` | number | Monthly search volume for the top keyword | +| ↳ `updateDate` | string | Date the SERP was last checked | + +### `ahrefs_rank_tracker_competitors_overview` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `projectId` | number | Yes | The Rank Tracker project ID \(found in the project URL in Ahrefs\) | +| `date` | string | Yes | Date to report rankings for, in YYYY-MM-DD format | +| `device` | string | Yes | Rankings device type: "desktop" or "mobile" | +| `dateCompared` | string | No | Comparison date in YYYY-MM-DD format, to compute position/traffic deltas | +| `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | +| `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `competitorKeywords` | array | Tracked keywords with competitor ranking data | +| ↳ `keyword` | string | The tracked keyword | +| ↳ `volume` | number | Average monthly search volume | +| ↳ `keywordDifficulty` | number | Keyword difficulty score \(0-100\) | +| ↳ `serpFeatures` | array | SERP features present in the results | +| ↳ `competitorsList` | array | Ranking data for each tracked competitor on this keyword | +| ↳ `url` | string | The competitor's ranking URL | +| ↳ `position` | number | Current ranking position | +| ↳ `bestPositionKind` | string | Type of the best position achieved | +| ↳ `traffic` | number | Estimated traffic to the competitor | +| ↳ `value` | number | Estimated traffic value | + +### `ahrefs_rank_tracker_competitors_stats` + +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. + +#### Input + +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `projectId` | number | Yes | The Rank Tracker project ID \(found in the project URL in Ahrefs\) | +| `date` | string | Yes | Date to report metrics for, in YYYY-MM-DD format | +| `device` | string | Yes | Rankings device type: "desktop" or "mobile" | +| `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | +| `apiKey` | string | Yes | Ahrefs API Key | + +#### Output + +| Parameter | Type | Description | +| --------- | ---- | ----------- | +| `competitorsStats` | array | Aggregate stats for each tracked competitor | +| ↳ `competitor` | string | The competitor's URL | +| ↳ `traffic` | number | Estimated monthly organic visits | +| ↳ `trafficValue` | number | Estimated monthly organic traffic value | +| ↳ `averagePosition` | number | Average top organic position across tracked keywords | +| ↳ `pos1To3` | number | Keywords ranking in top 3 positions | +| ↳ `pos4To10` | number | Keywords ranking in positions 4-10 | +| ↳ `shareOfVoice` | number | Organic traffic share percentage | +| ↳ `shareOfTrafficValue` | number | Organic traffic value share percentage | + diff --git a/apps/sim/blocks/blocks/ahrefs.ts b/apps/sim/blocks/blocks/ahrefs.ts index cd5b5e22347..8a0001b8a51 100644 --- a/apps/sim/blocks/blocks/ahrefs.ts +++ b/apps/sim/blocks/blocks/ahrefs.ts @@ -28,6 +28,52 @@ const MODE_OPTIONS = [ { label: 'Exact (exact URL)', id: 'exact' }, ] +const DEVICE_OPTIONS = [ + { label: 'Desktop', id: 'desktop' }, + { label: 'Mobile', id: 'mobile' }, +] + +const VOLUME_MODE_OPTIONS = [ + { label: 'Monthly', id: 'monthly' }, + { label: 'Average', id: 'average' }, +] + +const HISTORY_GROUPING_OPTIONS = [ + { label: 'Monthly', id: 'monthly' }, + { label: 'Weekly', id: 'weekly' }, + { label: 'Daily', id: 'daily' }, +] + +const PROTOCOL_OPTIONS = [ + { label: 'Both', id: 'both' }, + { label: 'HTTP', id: 'http' }, + { label: 'HTTPS', id: 'https' }, +] + +const RELATED_TERMS_OPTIONS = [ + { label: 'All', id: 'all' }, + { label: 'Also rank for', id: 'also_rank_for' }, + { label: 'Also talk about', id: 'also_talk_about' }, +] + +const VIEW_FOR_OPTIONS = [ + { label: 'Top 10 results', id: 'top_10' }, + { label: 'Top 100 results', id: 'top_100' }, +] + +const DATE_TIME_WAND_CONFIG = { + enabled: true, + prompt: `Generate a timestamp in YYYY-MM-DDThh:mm:ss format based on the user's description. +Examples: +- "today" -> Current date at 00:00:00 in YYYY-MM-DDThh:mm:ss format +- "yesterday" -> Yesterday's date at 00:00:00 in YYYY-MM-DDThh:mm:ss format +- "last week" -> Date 7 days ago at 00:00:00 in YYYY-MM-DDThh:mm:ss format + +Return ONLY the timestamp string in YYYY-MM-DDThh:mm:ss format - no explanations, no quotes, no extra text.`, + placeholder: 'Describe the timestamp (e.g., "today", "yesterday")...', + generationType: 'timestamp' as const, +} + const DATE_WAND_CONFIG = { enabled: true, prompt: `Generate a date in YYYY-MM-DD format based on the user's description. @@ -69,7 +115,23 @@ export const AhrefsBlock: BlockConfig = { { label: 'Organic Keywords', id: 'ahrefs_organic_keywords' }, { label: 'Organic Competitors', id: 'ahrefs_organic_competitors' }, { label: 'Top Pages', id: 'ahrefs_top_pages' }, + { label: 'Paid Pages', id: 'ahrefs_paid_pages' }, + { label: 'Anchors', id: 'ahrefs_anchors' }, { label: 'Keyword Overview', id: 'ahrefs_keyword_overview' }, + { label: 'Related Terms', id: 'ahrefs_related_terms' }, + { label: 'Domain Rating History', id: 'ahrefs_domain_rating_history' }, + { label: 'Metrics History', id: 'ahrefs_metrics_history' }, + { label: 'Referring Domains History', id: 'ahrefs_refdomains_history' }, + { label: 'Keywords History', id: 'ahrefs_keywords_history' }, + { label: 'Batch Analysis', id: 'ahrefs_batch_analysis' }, + { label: 'Site Audit Page Explorer', id: 'ahrefs_site_audit_page_explorer' }, + { label: 'Rank Tracker Overview', id: 'ahrefs_rank_tracker_overview' }, + { label: 'Rank Tracker SERP Overview', id: 'ahrefs_rank_tracker_serp_overview' }, + { + label: 'Rank Tracker Competitors Overview', + id: 'ahrefs_rank_tracker_competitors_overview', + }, + { label: 'Rank Tracker Competitors Stats', id: 'ahrefs_rank_tracker_competitors_stats' }, ], value: () => 'ahrefs_domain_rating', }, @@ -366,11 +428,7 @@ export const AhrefsBlock: BlockConfig = { 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' }, - ], + options: MODE_OPTIONS, value: () => 'domain', condition: { field: 'operation', value: 'ahrefs_top_pages' }, mode: 'advanced', @@ -410,6 +468,627 @@ export const AhrefsBlock: BlockConfig = { condition: { field: 'operation', value: 'ahrefs_keyword_overview' }, mode: 'advanced', }, + // Paid Pages operation inputs + { + id: 'target', + title: 'Target Domain', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_paid_pages' }, + required: true, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_paid_pages' }, + mode: 'advanced', + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'domain', + condition: { field: 'operation', value: 'ahrefs_paid_pages' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_paid_pages' }, + mode: 'advanced', + }, + { + id: 'date', + title: 'Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD (defaults to today)', + condition: { field: 'operation', value: 'ahrefs_paid_pages' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + // Anchors operation inputs + { + id: 'target', + title: 'Target Domain/URL', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_anchors' }, + required: true, + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'domain', + condition: { field: 'operation', value: 'ahrefs_anchors' }, + 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_anchors' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_anchors' }, + mode: 'advanced', + }, + // Related Terms operation inputs + { + id: 'keyword', + title: 'Keyword', + type: 'short-input', + placeholder: 'Enter seed keyword', + condition: { field: 'operation', value: 'ahrefs_related_terms' }, + required: true, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_related_terms' }, + mode: 'advanced', + }, + { + id: 'terms', + title: 'Related Terms Type', + type: 'dropdown', + options: RELATED_TERMS_OPTIONS, + value: () => 'all', + condition: { field: 'operation', value: 'ahrefs_related_terms' }, + mode: 'advanced', + }, + { + id: 'viewFor', + title: 'Derive From', + type: 'dropdown', + options: VIEW_FOR_OPTIONS, + value: () => 'top_10', + condition: { field: 'operation', value: 'ahrefs_related_terms' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_related_terms' }, + mode: 'advanced', + }, + // Domain Rating History operation inputs + { + id: 'target', + title: 'Target Domain', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_domain_rating_history' }, + required: true, + }, + { + id: 'dateFrom', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_domain_rating_history' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'dateTo', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD (defaults to today)', + condition: { field: 'operation', value: 'ahrefs_domain_rating_history' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'historyGrouping', + title: 'Grouping', + type: 'dropdown', + options: HISTORY_GROUPING_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_domain_rating_history' }, + mode: 'advanced', + }, + // Metrics History operation inputs + { + id: 'target', + title: 'Target Domain/URL', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + required: true, + }, + { + id: 'dateFrom', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'dateTo', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD (defaults to today)', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + mode: 'advanced', + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'domain', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + mode: 'advanced', + }, + { + id: 'volumeMode', + title: 'Volume Mode', + type: 'dropdown', + options: VOLUME_MODE_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + mode: 'advanced', + }, + { + id: 'historyGrouping', + title: 'Grouping', + type: 'dropdown', + options: HISTORY_GROUPING_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_metrics_history' }, + mode: 'advanced', + }, + // Referring Domains History operation inputs + { + id: 'target', + title: 'Target Domain/URL', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_refdomains_history' }, + required: true, + }, + { + id: 'dateFrom', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_refdomains_history' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'dateTo', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD (defaults to today)', + condition: { field: 'operation', value: 'ahrefs_refdomains_history' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'domain', + condition: { field: 'operation', value: 'ahrefs_refdomains_history' }, + mode: 'advanced', + }, + { + id: 'historyGrouping', + title: 'Grouping', + type: 'dropdown', + options: HISTORY_GROUPING_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_refdomains_history' }, + mode: 'advanced', + }, + // Keywords History operation inputs + { + id: 'target', + title: 'Target Domain/URL', + type: 'short-input', + placeholder: 'example.com', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + required: true, + }, + { + id: 'dateFrom', + title: 'Start Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'dateTo', + title: 'End Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD (defaults to today)', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + mode: 'advanced', + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'domain', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + mode: 'advanced', + }, + { + id: 'historyGrouping', + title: 'Grouping', + type: 'dropdown', + options: HISTORY_GROUPING_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_keywords_history' }, + mode: 'advanced', + }, + // Batch Analysis operation inputs + { + id: 'targets', + title: 'Targets', + type: 'long-input', + placeholder: 'example.com, competitor.com, another-site.com', + condition: { field: 'operation', value: 'ahrefs_batch_analysis' }, + required: true, + }, + { + id: 'mode', + title: 'Analysis Mode', + type: 'dropdown', + options: MODE_OPTIONS, + value: () => 'subdomains', + condition: { field: 'operation', value: 'ahrefs_batch_analysis' }, + mode: 'advanced', + }, + { + id: 'protocol', + title: 'Protocol', + type: 'dropdown', + options: PROTOCOL_OPTIONS, + value: () => 'both', + condition: { field: 'operation', value: 'ahrefs_batch_analysis' }, + mode: 'advanced', + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_batch_analysis' }, + mode: 'advanced', + }, + { + id: 'volumeMode', + title: 'Volume Mode', + type: 'dropdown', + options: VOLUME_MODE_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_batch_analysis' }, + mode: 'advanced', + }, + // Site Audit Page Explorer operation inputs + { + id: 'projectId', + title: 'Site Audit Project ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'ahrefs_site_audit_page_explorer' }, + required: true, + }, + { + id: 'issueId', + title: 'Issue ID', + type: 'short-input', + placeholder: 'Only show pages affected by this issue', + condition: { field: 'operation', value: 'ahrefs_site_audit_page_explorer' }, + mode: 'advanced', + }, + { + id: 'date', + title: 'Crawl Date', + type: 'short-input', + placeholder: 'YYYY-MM-DDThh:mm:ss (defaults to most recent crawl)', + condition: { field: 'operation', value: 'ahrefs_site_audit_page_explorer' }, + mode: 'advanced', + wandConfig: DATE_TIME_WAND_CONFIG, + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_site_audit_page_explorer' }, + mode: 'advanced', + }, + { + id: 'offset', + title: 'Offset', + type: 'short-input', + placeholder: '0', + condition: { field: 'operation', value: 'ahrefs_site_audit_page_explorer' }, + mode: 'advanced', + }, + // Rank Tracker Overview operation inputs + { + id: 'projectId', + title: 'Rank Tracker Project ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + required: true, + }, + { + id: 'date', + title: 'Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'device', + title: 'Device', + type: 'dropdown', + options: DEVICE_OPTIONS, + value: () => 'desktop', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + required: true, + }, + { + id: 'dateCompared', + title: 'Compare To Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'volumeMode', + title: 'Volume Mode', + type: 'dropdown', + options: VOLUME_MODE_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_overview' }, + mode: 'advanced', + }, + // Rank Tracker SERP Overview operation inputs + { + id: 'projectId', + title: 'Rank Tracker Project ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + required: true, + }, + { + id: 'keyword', + title: 'Keyword', + type: 'short-input', + placeholder: 'Enter tracked keyword', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + required: true, + }, + { + id: 'country', + title: 'Country', + type: 'dropdown', + options: COUNTRY_OPTIONS, + value: () => 'us', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + required: true, + }, + { + id: 'device', + title: 'Device', + type: 'dropdown', + options: DEVICE_OPTIONS, + value: () => 'desktop', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + required: true, + }, + { + id: 'topPositions', + title: 'Top Positions', + type: 'short-input', + placeholder: 'All available positions', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + mode: 'advanced', + }, + { + id: 'date', + title: 'As Of', + type: 'short-input', + placeholder: 'YYYY-MM-DDThh:mm:ss (defaults to latest)', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + mode: 'advanced', + wandConfig: DATE_TIME_WAND_CONFIG, + }, + { + id: 'locationId', + title: 'Location ID', + type: 'short-input', + placeholder: 'Optional tracked location ID', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + mode: 'advanced', + }, + { + id: 'languageCode', + title: 'Language Code', + type: 'short-input', + placeholder: 'Optional tracked language code', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_serp_overview' }, + mode: 'advanced', + }, + // Rank Tracker Competitors Overview operation inputs + { + id: 'projectId', + title: 'Rank Tracker Project ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + required: true, + }, + { + id: 'date', + title: 'Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'device', + title: 'Device', + type: 'dropdown', + options: DEVICE_OPTIONS, + value: () => 'desktop', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + required: true, + }, + { + id: 'dateCompared', + title: 'Compare To Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + mode: 'advanced', + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'volumeMode', + title: 'Volume Mode', + type: 'dropdown', + options: VOLUME_MODE_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + mode: 'advanced', + }, + { + id: 'limit', + title: 'Limit', + type: 'short-input', + placeholder: '1000', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_overview' }, + mode: 'advanced', + }, + // Rank Tracker Competitors Stats operation inputs + { + id: 'projectId', + title: 'Rank Tracker Project ID', + type: 'short-input', + placeholder: '12345', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_stats' }, + required: true, + }, + { + id: 'date', + title: 'Date', + type: 'short-input', + placeholder: 'YYYY-MM-DD', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_stats' }, + required: true, + wandConfig: DATE_WAND_CONFIG, + }, + { + id: 'device', + title: 'Device', + type: 'dropdown', + options: DEVICE_OPTIONS, + value: () => 'desktop', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_stats' }, + required: true, + }, + { + id: 'volumeMode', + title: 'Volume Mode', + type: 'dropdown', + options: VOLUME_MODE_OPTIONS, + value: () => 'monthly', + condition: { field: 'operation', value: 'ahrefs_rank_tracker_competitors_stats' }, + mode: 'advanced', + }, // API Key (common to all operations) { id: 'apiKey', @@ -432,6 +1111,19 @@ export const AhrefsBlock: BlockConfig = { 'ahrefs_organic_competitors', 'ahrefs_top_pages', 'ahrefs_keyword_overview', + 'ahrefs_paid_pages', + 'ahrefs_anchors', + 'ahrefs_related_terms', + 'ahrefs_domain_rating_history', + 'ahrefs_metrics_history', + 'ahrefs_refdomains_history', + 'ahrefs_keywords_history', + 'ahrefs_batch_analysis', + 'ahrefs_site_audit_page_explorer', + 'ahrefs_rank_tracker_overview', + 'ahrefs_rank_tracker_serp_overview', + 'ahrefs_rank_tracker_competitors_overview', + 'ahrefs_rank_tracker_competitors_stats', ], config: { tool: (params) => { @@ -456,6 +1148,32 @@ export const AhrefsBlock: BlockConfig = { return 'ahrefs_top_pages' case 'ahrefs_keyword_overview': return 'ahrefs_keyword_overview' + case 'ahrefs_paid_pages': + return 'ahrefs_paid_pages' + case 'ahrefs_anchors': + return 'ahrefs_anchors' + case 'ahrefs_related_terms': + return 'ahrefs_related_terms' + case 'ahrefs_domain_rating_history': + return 'ahrefs_domain_rating_history' + case 'ahrefs_metrics_history': + return 'ahrefs_metrics_history' + case 'ahrefs_refdomains_history': + return 'ahrefs_refdomains_history' + case 'ahrefs_keywords_history': + return 'ahrefs_keywords_history' + case 'ahrefs_batch_analysis': + return 'ahrefs_batch_analysis' + case 'ahrefs_site_audit_page_explorer': + return 'ahrefs_site_audit_page_explorer' + case 'ahrefs_rank_tracker_overview': + return 'ahrefs_rank_tracker_overview' + case 'ahrefs_rank_tracker_serp_overview': + return 'ahrefs_rank_tracker_serp_overview' + case 'ahrefs_rank_tracker_competitors_overview': + return 'ahrefs_rank_tracker_competitors_overview' + case 'ahrefs_rank_tracker_competitors_stats': + return 'ahrefs_rank_tracker_competitors_stats' default: return 'ahrefs_domain_rating' } @@ -463,6 +1181,10 @@ export const AhrefsBlock: BlockConfig = { params: (params) => { const result: Record = {} if (params.limit) result.limit = Number(params.limit) + if (params.projectId) result.projectId = Number(params.projectId) + if (params.topPositions) result.topPositions = Number(params.topPositions) + if (params.locationId) result.locationId = Number(params.locationId) + if (params.offset) result.offset = Number(params.offset) return result }, }, @@ -480,6 +1202,37 @@ export const AhrefsBlock: BlockConfig = { description: 'Historical scope for backlink-profile endpoints (all_time, live)', }, limit: { type: 'number', description: 'Maximum number of results to return' }, + targets: { + type: 'string', + description: 'Comma-separated list of domains or URLs for batch analysis', + }, + protocol: { type: 'string', description: 'Protocol filter (both, http, https)' }, + volumeMode: { + type: 'string', + description: 'Search volume calculation mode (monthly, average)', + }, + projectId: { type: 'number', description: 'Ahrefs Rank Tracker or Site Audit project ID' }, + device: { type: 'string', description: 'Rankings device type (desktop, mobile)' }, + dateCompared: { type: 'string', description: 'Comparison date in YYYY-MM-DD format' }, + topPositions: { type: 'number', description: 'Number of top organic positions to return' }, + locationId: { type: 'number', description: 'Tracked keyword location ID' }, + languageCode: { type: 'string', description: 'Tracked keyword language code' }, + dateFrom: { type: 'string', description: 'Start date of a historical period (YYYY-MM-DD)' }, + dateTo: { type: 'string', description: 'End date of a historical period (YYYY-MM-DD)' }, + historyGrouping: { + type: 'string', + description: 'Time interval for grouping historical data (daily, weekly, monthly)', + }, + terms: { + type: 'string', + description: 'Type of related keywords to return (also_rank_for, also_talk_about, all)', + }, + viewFor: { + type: 'string', + description: 'Whether to derive related terms from top 10 or top 100 ranking pages', + }, + issueId: { type: 'string', description: 'Site Audit issue ID to filter affected pages' }, + offset: { type: 'number', description: 'Number of results to skip, for pagination' }, }, outputs: { // Domain Rating output @@ -515,6 +1268,59 @@ export const AhrefsBlock: BlockConfig = { description: 'Keyword metrics overview, including search intent flags (informational, navigational, commercial, transactional, branded, local)', }, + // Paid Pages output + paidPages: { type: 'json', description: 'List of pages receiving paid search traffic' }, + // Anchors output + anchors: { type: 'json', description: 'Anchor text distribution for the backlink profile' }, + // Related Terms output + relatedTerms: { type: 'json', description: 'Related keyword ideas for the seed keyword' }, + // Domain Rating History output + domainRatings: { type: 'json', description: 'Historical Domain Rating data points' }, + // Metrics History output + metricsHistory: { + type: 'json', + description: 'Historical organic and paid traffic data points', + }, + // Referring Domains History output + referringDomainsHistory: { + type: 'json', + description: 'Historical referring domains count data points', + }, + // Keywords History output + keywordsHistory: { + type: 'json', + description: 'Historical organic keyword ranking distribution', + }, + // Batch Analysis output + results: { + type: 'json', + description: 'Bulk SEO metrics for each analyzed target, in submission order', + }, + // Site Audit Page Explorer output + auditPages: { + type: 'json', + description: 'Crawled pages with health and SEO metrics from a Site Audit project', + }, + // Rank Tracker Overview output + overviews: { + type: 'json', + description: 'Ranking overview for each keyword tracked in a Rank Tracker project', + }, + // Rank Tracker SERP Overview output + positions: { + type: 'json', + description: 'Every ranking result on the SERP for a tracked keyword', + }, + // Rank Tracker Competitors Overview output + competitorKeywords: { + type: 'json', + description: 'Tracked keywords with competitor ranking, traffic, and traffic value data', + }, + // Rank Tracker Competitors Stats output + competitorsStats: { + type: 'json', + description: 'Aggregate stats for each tracked Rank Tracker competitor', + }, }, } @@ -600,6 +1406,35 @@ export const AhrefsBlockMeta = { tags: ['marketing', 'monitoring', 'reporting'], alsoIntegrations: ['slack'], }, + { + icon: AhrefsIcon, + title: 'Ahrefs Rank Tracker daily digest', + prompt: + 'Build a scheduled daily workflow that pulls the Ahrefs Rank Tracker overview for my project, has an agent summarize position and traffic movers, and posts the digest to Slack.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'reporting', 'monitoring'], + alsoIntegrations: ['slack'], + }, + { + icon: AhrefsIcon, + title: 'Ahrefs batch competitor snapshot', + prompt: + 'Create a workflow that runs an Ahrefs batch analysis across a list of competitor domains, writes the resulting Domain Rating, backlinks, and organic traffic to a comparison table, and has an agent call out the biggest gaps.', + modules: ['tables', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'research'], + }, + { + icon: AhrefsIcon, + title: 'Ahrefs Site Audit issue tracker', + prompt: + 'Build a scheduled weekly workflow that pulls crawled pages from an Ahrefs Site Audit project, writes non-compliant or broken pages to a remediation table, and posts a Slack summary for the SEO lead.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'monitoring', 'automation'], + alsoIntegrations: ['slack'], + }, ], skills: [ { diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 449e5c883d8..84165e5364e 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-07-02", + "updatedAt": "2026-07-06", "integrations": [ { "type": "onepassword", @@ -387,12 +387,64 @@ "name": "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." }, + { + "name": "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." + }, + { + "name": "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." + }, { "name": "Keyword Overview", "description": "Get detailed metrics for a keyword including search volume, keyword difficulty, CPC, clicks, and traffic potential." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." + }, + { + "name": "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." } ], - "operationCount": 10, + "operationCount": 23, "triggers": [], "triggerCount": 0, "authType": "api-key", diff --git a/apps/sim/tools/ahrefs/anchors.ts b/apps/sim/tools/ahrefs/anchors.ts new file mode 100644 index 00000000000..f0851fbfb09 --- /dev/null +++ b/apps/sim/tools/ahrefs/anchors.ts @@ -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 = { + 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, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/batch_analysis.ts b/apps/sim/tools/ahrefs/batch_analysis.ts new file mode 100644 index 00000000000..eb051c81ac2 --- /dev/null +++ b/apps/sim/tools/ahrefs/batch_analysis.ts @@ -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 = + { + 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"', + }, + 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, + ...(params.country ? { country: params.country } : {}), + 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 || data.results || []).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, + }, + }, + }, + }, + }, + } diff --git a/apps/sim/tools/ahrefs/broken_backlinks.ts b/apps/sim/tools/ahrefs/broken_backlinks.ts index 3114356759a..af008eff858 100644 --- a/apps/sim/tools/ahrefs/broken_backlinks.ts +++ b/apps/sim/tools/ahrefs/broken_backlinks.ts @@ -4,7 +4,7 @@ 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' +const SELECT_FIELDS = 'url_from,url_to,http_code,anchor,domain_rating_source' export const brokenBacklinksTool: ToolConfig< AhrefsBrokenBacklinksParams, @@ -71,7 +71,7 @@ export const brokenBacklinksTool: ToolConfig< const brokenBacklinks = (data.backlinks || []).map((link: any) => ({ urlFrom: link.url_from || '', urlTo: link.url_to || '', - httpCode: link.http_code_target ?? null, + httpCode: link.http_code ?? null, anchor: link.anchor || '', domainRatingSource: link.domain_rating_source ?? 0, })) diff --git a/apps/sim/tools/ahrefs/domain_rating_history.ts b/apps/sim/tools/ahrefs/domain_rating_history.ts new file mode 100644 index 00000000000..a06763e4031 --- /dev/null +++ b/apps/sim/tools/ahrefs/domain_rating_history.ts @@ -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' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/index.ts b/apps/sim/tools/ahrefs/index.ts index cc68f7a1296..701683f334a 100644 --- a/apps/sim/tools/ahrefs/index.ts +++ b/apps/sim/tools/ahrefs/index.ts @@ -1,12 +1,25 @@ +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 @@ -19,5 +32,18 @@ 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' diff --git a/apps/sim/tools/ahrefs/keyword_overview.ts b/apps/sim/tools/ahrefs/keyword_overview.ts index 632e8522409..4b54c0f5292 100644 --- a/apps/sim/tools/ahrefs/keyword_overview.ts +++ b/apps/sim/tools/ahrefs/keyword_overview.ts @@ -69,7 +69,8 @@ export const keywordOverviewTool: ToolConfig< keyword: result.keyword || '', searchVolume: result.volume ?? 0, keywordDifficulty: result.difficulty ?? null, - cpc: result.cpc ?? null, + // Ahrefs returns cpc in USD cents; convert to USD to match the documented unit + 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, diff --git a/apps/sim/tools/ahrefs/keywords_history.ts b/apps/sim/tools/ahrefs/keywords_history.ts new file mode 100644 index 00000000000..7f7b194ffc7 --- /dev/null +++ b/apps/sim/tools/ahrefs/keywords_history.ts @@ -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"', + }, + 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) + if (params.country) url.searchParams.set('country', params.country) + 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' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/metrics.ts b/apps/sim/tools/ahrefs/metrics.ts index 99184349d74..df71f6c8f75 100644 --- a/apps/sim/tools/ahrefs/metrics.ts +++ b/apps/sim/tools/ahrefs/metrics.ts @@ -76,11 +76,11 @@ export const metricsTool: ToolConfig organicTraffic: metrics.org_traffic ?? 0, organicKeywords: metrics.org_keywords ?? 0, organicKeywordsTop3: metrics.org_keywords_1_3 ?? 0, - organicCost: metrics.org_cost ?? null, + 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: metrics.paid_cost ?? null, + paidCost: typeof metrics.paid_cost === 'number' ? metrics.paid_cost / 100 : null, }, }, } diff --git a/apps/sim/tools/ahrefs/metrics_history.ts b/apps/sim/tools/ahrefs/metrics_history.ts new file mode 100644 index 00000000000..99a0769c075 --- /dev/null +++ b/apps/sim/tools/ahrefs/metrics_history.ts @@ -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"', + }, + 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) + if (params.country) url.searchParams.set('country', params.country) + 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, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/paid_pages.ts b/apps/sim/tools/ahrefs/paid_pages.ts new file mode 100644 index 00000000000..6aec0ec5e11 --- /dev/null +++ b/apps/sim/tools/ahrefs/paid_pages.ts @@ -0,0 +1,135 @@ +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 = { + 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"', + }, + 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) + if (params.country) url.searchParams.set('country', params.country) + 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, + // Ahrefs returns value in USD cents; convert to USD to match the documented unit + 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, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts new file mode 100644 index 00000000000..2c1a6f81ac2 --- /dev/null +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts @@ -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: competitor.value ?? 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', + optional: true, + }, + }, + }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts new file mode 100644 index 00000000000..0a2c8d373ce --- /dev/null +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts @@ -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: item.traffic_value ?? 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', + 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', + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/rank_tracker_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_overview.ts new file mode 100644 index 00000000000..a0105be81fc --- /dev/null +++ b/apps/sim/tools/ahrefs/rank_tracker_overview.ts @@ -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, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts new file mode 100644 index 00000000000..f18127ce2c3 --- /dev/null +++ b/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts @@ -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: item.value ?? 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 in USD cents', + 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' }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/refdomains_history.ts b/apps/sim/tools/ahrefs/refdomains_history.ts new file mode 100644 index 00000000000..a2bcc993196 --- /dev/null +++ b/apps/sim/tools/ahrefs/refdomains_history.ts @@ -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', + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/related_terms.ts b/apps/sim/tools/ahrefs/related_terms.ts new file mode 100644 index 00000000000..5b273fbfdb8 --- /dev/null +++ b/apps/sim/tools/ahrefs/related_terms.ts @@ -0,0 +1,140 @@ +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 = { + 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, + // Ahrefs returns cpc in USD cents; convert to USD to match the documented unit + 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' }, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/site_audit_page_explorer.ts b/apps/sim/tools/ahrefs/site_audit_page_explorer.ts new file mode 100644 index 00000000000..76ef8c70ac1 --- /dev/null +++ b/apps/sim/tools/ahrefs/site_audit_page_explorer.ts @@ -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, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/types.ts b/apps/sim/tools/ahrefs/types.ts index cb1ff854ff6..69e0ed567f1 100644 --- a/apps/sim/tools/ahrefs/types.ts +++ b/apps/sim/tools/ahrefs/types.ts @@ -242,6 +242,346 @@ export interface AhrefsOrganicCompetitorsResponse extends ToolResponse { } } +// 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 | 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 @@ -254,3 +594,16 @@ export type AhrefsResponse = | AhrefsBrokenBacklinksResponse | AhrefsMetricsResponse | AhrefsOrganicCompetitorsResponse + | AhrefsRankTrackerOverviewResponse + | AhrefsRankTrackerSerpOverviewResponse + | AhrefsRankTrackerCompetitorsOverviewResponse + | AhrefsRankTrackerCompetitorsStatsResponse + | AhrefsBatchAnalysisResponse + | AhrefsSiteAuditPageExplorerResponse + | AhrefsDomainRatingHistoryResponse + | AhrefsMetricsHistoryResponse + | AhrefsRefdomainsHistoryResponse + | AhrefsKeywordsHistoryResponse + | AhrefsRelatedTermsResponse + | AhrefsAnchorsResponse + | AhrefsPaidPagesResponse diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index c293f27acf2..3b7333effc1 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -67,15 +67,28 @@ import { agiloftUpdateRecordTool, } from '@/tools/agiloft' import { + ahrefsAnchorsTool, ahrefsBacklinksStatsTool, ahrefsBacklinksTool, + ahrefsBatchAnalysisTool, ahrefsBrokenBacklinksTool, + ahrefsDomainRatingHistoryTool, ahrefsDomainRatingTool, ahrefsKeywordOverviewTool, + ahrefsKeywordsHistoryTool, + ahrefsMetricsHistoryTool, ahrefsMetricsTool, ahrefsOrganicCompetitorsTool, ahrefsOrganicKeywordsTool, + ahrefsPaidPagesTool, + ahrefsRankTrackerCompetitorsOverviewTool, + ahrefsRankTrackerCompetitorsStatsTool, + ahrefsRankTrackerOverviewTool, + ahrefsRankTrackerSerpOverviewTool, + ahrefsRefdomainsHistoryTool, ahrefsReferringDomainsTool, + ahrefsRelatedTermsTool, + ahrefsSiteAuditPageExplorerTool, ahrefsTopPagesTool, } from '@/tools/ahrefs' import { @@ -6810,15 +6823,28 @@ export const tools: Record = { attio_update_record: attioUpdateRecordTool, attio_update_task: attioUpdateTaskTool, attio_update_webhook: attioUpdateWebhookTool, + ahrefs_anchors: ahrefsAnchorsTool, ahrefs_backlinks: ahrefsBacklinksTool, ahrefs_backlinks_stats: ahrefsBacklinksStatsTool, + ahrefs_batch_analysis: ahrefsBatchAnalysisTool, ahrefs_broken_backlinks: ahrefsBrokenBacklinksTool, ahrefs_domain_rating: ahrefsDomainRatingTool, + ahrefs_domain_rating_history: ahrefsDomainRatingHistoryTool, ahrefs_keyword_overview: ahrefsKeywordOverviewTool, + ahrefs_keywords_history: ahrefsKeywordsHistoryTool, ahrefs_metrics: ahrefsMetricsTool, + ahrefs_metrics_history: ahrefsMetricsHistoryTool, ahrefs_organic_competitors: ahrefsOrganicCompetitorsTool, ahrefs_organic_keywords: ahrefsOrganicKeywordsTool, + ahrefs_paid_pages: ahrefsPaidPagesTool, + ahrefs_rank_tracker_competitors_overview: ahrefsRankTrackerCompetitorsOverviewTool, + ahrefs_rank_tracker_competitors_stats: ahrefsRankTrackerCompetitorsStatsTool, + ahrefs_rank_tracker_overview: ahrefsRankTrackerOverviewTool, + ahrefs_rank_tracker_serp_overview: ahrefsRankTrackerSerpOverviewTool, + ahrefs_refdomains_history: ahrefsRefdomainsHistoryTool, ahrefs_referring_domains: ahrefsReferringDomainsTool, + ahrefs_related_terms: ahrefsRelatedTermsTool, + ahrefs_site_audit_page_explorer: ahrefsSiteAuditPageExplorerTool, ahrefs_top_pages: ahrefsTopPagesTool, apify_run_actor_sync: apifyRunActorSyncTool, apify_run_actor_async: apifyRunActorAsyncTool, From dab60b948ad908e9fa1dd7206574afb6e3e9817a Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:07:28 -0700 Subject: [PATCH 2/8] fix(ahrefs): convert remaining cents-to-USD fields, drop unneeded fallback key Rank Tracker SERP Overview's value field, Competitors Stats' trafficValue, and Competitors Overview's nested competitor value were all left in USD cents while every other monetary field in the integration converts to USD - fixed for consistency with the rest of the integration. Also drops the unverified `data.results` fallback in Batch Analysis; the Ahrefs v3 docs confirm the response key is always `targets`. --- apps/sim/tools/ahrefs/batch_analysis.ts | 2 +- apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/sim/tools/ahrefs/batch_analysis.ts b/apps/sim/tools/ahrefs/batch_analysis.ts index eb051c81ac2..99a99bbac5c 100644 --- a/apps/sim/tools/ahrefs/batch_analysis.ts +++ b/apps/sim/tools/ahrefs/batch_analysis.ts @@ -88,7 +88,7 @@ export const batchAnalysisTool: ToolConfig ({ + const results = (data.targets || []).map((item: any) => ({ url: item.url || '', index: item.index ?? 0, domainRating: item.domain_rating ?? null, diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts index 2c1a6f81ac2..a78f8e1c2a5 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts @@ -99,7 +99,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< position: competitor.position ?? null, bestPositionKind: competitor.best_position_kind ?? null, traffic: competitor.traffic ?? null, - value: competitor.value ?? null, + value: typeof competitor.value === 'number' ? competitor.value / 100 : null, })), })) @@ -154,7 +154,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< }, value: { type: 'number', - description: 'Estimated traffic value', + description: 'Estimated traffic value (USD)', optional: true, }, }, diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts index 0a2c8d373ce..d8b8ab39143 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts @@ -79,7 +79,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< const competitorsStats = (data['competitors-metrics'] || []).map((item: any) => ({ competitor: item.competitor || '', traffic: item.traffic ?? null, - trafficValue: item.traffic_value ?? 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, @@ -110,7 +110,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< }, trafficValue: { type: 'number', - description: 'Estimated monthly organic traffic value', + description: 'Estimated monthly organic traffic value (USD)', optional: true, }, averagePosition: { diff --git a/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts index f18127ce2c3..a1b72f7de04 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_serp_overview.ts @@ -109,7 +109,7 @@ export const rankTrackerSerpOverviewTool: ToolConfig< backlinks: item.backlinks ?? 0, refdomains: item.refdomains ?? 0, traffic: item.traffic ?? 0, - value: item.value ?? null, + value: typeof item.value === 'number' ? item.value / 100 : null, topKeyword: item.top_keyword ?? null, topKeywordVolume: item.top_keyword_volume ?? null, updateDate: item.update_date || '', @@ -145,7 +145,7 @@ export const rankTrackerSerpOverviewTool: ToolConfig< traffic: { type: 'number', description: 'Estimated monthly organic search traffic' }, value: { type: 'number', - description: 'Estimated monthly traffic value in USD cents', + description: 'Estimated monthly traffic value (USD)', optional: true, }, topKeyword: { From 3c3d8bf8e8d8664fa1a417e46ee286aaa57d0404 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:16:33 -0700 Subject: [PATCH 3/8] docs(ahrefs): regenerate docs to reflect cents-to-USD conversion fix The generated docs page was stale after the previous commit converted rank_tracker_serp_overview.value, rank_tracker_competitors_stats.trafficValue, and rank_tracker_competitors_overview's nested value from cents to USD - regenerating picks up the corrected field descriptions. --- apps/docs/content/docs/en/integrations/ahrefs.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index e037da245c0..cdaba0e7e0e 100644 --- a/apps/docs/content/docs/en/integrations/ahrefs.mdx +++ b/apps/docs/content/docs/en/integrations/ahrefs.mdx @@ -598,7 +598,7 @@ Get the full SERP (search engine results page) for a keyword tracked in an Ahref | ↳ `backlinks` | number | Total backlinks to the ranking domain | | ↳ `refdomains` | number | Unique referring domains | | ↳ `traffic` | number | Estimated monthly organic search traffic | -| ↳ `value` | number | Estimated monthly traffic value in USD cents | +| ↳ `value` | number | Estimated monthly traffic value \(USD\) | | ↳ `topKeyword` | string | Highest-traffic keyword ranking for this page | | ↳ `topKeywordVolume` | number | Monthly search volume for the top keyword | | ↳ `updateDate` | string | Date the SERP was last checked | @@ -633,7 +633,7 @@ Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker proje | ↳ `position` | number | Current ranking position | | ↳ `bestPositionKind` | string | Type of the best position achieved | | ↳ `traffic` | number | Estimated traffic to the competitor | -| ↳ `value` | number | Estimated traffic value | +| ↳ `value` | number | Estimated traffic value \(USD\) | ### `ahrefs_rank_tracker_competitors_stats` @@ -656,7 +656,7 @@ Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competit | `competitorsStats` | array | Aggregate stats for each tracked competitor | | ↳ `competitor` | string | The competitor's URL | | ↳ `traffic` | number | Estimated monthly organic visits | -| ↳ `trafficValue` | number | Estimated monthly organic traffic value | +| ↳ `trafficValue` | number | Estimated monthly organic traffic value \(USD\) | | ↳ `averagePosition` | number | Average top organic position across tracked keywords | | ↳ `pos1To3` | number | Keywords ranking in top 3 positions | | ↳ `pos4To10` | number | Keywords ranking in positions 4-10 | From e4dcffc936ca53659ad67dd34fb672fe6e1ac6d2 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:23:09 -0700 Subject: [PATCH 4/8] fix(ahrefs): revert incorrect broken_backlinks column, fix missed top_pages conversion, revert unverified competitor value conversions Independent re-verification against live Ahrefs v3 docs surfaced two real regressions from the earlier fix rounds and one missed conversion: - broken_backlinks: the earlier fix changed the selected column from http_code_target to http_code, but the docs say http_code is the *referring page's* status and http_code_target is the *broken target page's* status - the tool needs the latter (matches its own output description). Reverted to http_code_target. - top_pages: value field was never divided by 100 despite the docs stating it's in USD cents and the output already claiming USD - fixed. - rank_tracker_competitors_stats.trafficValue and the nested competitor.value in rank_tracker_competitors_overview were converted from cents to USD last round on a "match every other monetary field" assumption, but the live docs do not document these two fields as cents (unlike every field that was correctly converted). Reverted to passthrough and dropped the "(USD)" claim from their descriptions until Ahrefs documents the unit. Also added the missing "exact" mode option to top_pages' mode param description, matching every sibling tool. --- apps/docs/content/docs/en/integrations/ahrefs.mdx | 6 +++--- apps/sim/tools/ahrefs/broken_backlinks.ts | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts | 4 ++-- apps/sim/tools/ahrefs/top_pages.ts | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index cdaba0e7e0e..ca38e1dd6ea 100644 --- a/apps/docs/content/docs/en/integrations/ahrefs.mdx +++ b/apps/docs/content/docs/en/integrations/ahrefs.mdx @@ -244,7 +244,7 @@ Get the top pages of a target domain sorted by organic traffic. Returns page URL | --------- | ---- | -------- | ----------- | | `target` | string | Yes | The target domain to analyze. Example: "example.com" | | `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" \(default: "us"\) | -| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\). Example: "domain" | +| `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\). Example: "domain" | | `date` | string | No | Date to report metrics on, in YYYY-MM-DD format \(defaults to today\) | | `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | | `apiKey` | string | Yes | Ahrefs API Key | @@ -633,7 +633,7 @@ Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker proje | ↳ `position` | number | Current ranking position | | ↳ `bestPositionKind` | string | Type of the best position achieved | | ↳ `traffic` | number | Estimated traffic to the competitor | -| ↳ `value` | number | Estimated traffic value \(USD\) | +| ↳ `value` | number | Estimated traffic value | ### `ahrefs_rank_tracker_competitors_stats` @@ -656,7 +656,7 @@ Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competit | `competitorsStats` | array | Aggregate stats for each tracked competitor | | ↳ `competitor` | string | The competitor's URL | | ↳ `traffic` | number | Estimated monthly organic visits | -| ↳ `trafficValue` | number | Estimated monthly organic traffic value \(USD\) | +| ↳ `trafficValue` | number | Estimated monthly organic traffic value | | ↳ `averagePosition` | number | Average top organic position across tracked keywords | | ↳ `pos1To3` | number | Keywords ranking in top 3 positions | | ↳ `pos4To10` | number | Keywords ranking in positions 4-10 | diff --git a/apps/sim/tools/ahrefs/broken_backlinks.ts b/apps/sim/tools/ahrefs/broken_backlinks.ts index af008eff858..3114356759a 100644 --- a/apps/sim/tools/ahrefs/broken_backlinks.ts +++ b/apps/sim/tools/ahrefs/broken_backlinks.ts @@ -4,7 +4,7 @@ import type { } from '@/tools/ahrefs/types' import type { ToolConfig } from '@/tools/types' -const SELECT_FIELDS = 'url_from,url_to,http_code,anchor,domain_rating_source' +const SELECT_FIELDS = 'url_from,url_to,http_code_target,anchor,domain_rating_source' export const brokenBacklinksTool: ToolConfig< AhrefsBrokenBacklinksParams, @@ -71,7 +71,7 @@ export const brokenBacklinksTool: ToolConfig< const brokenBacklinks = (data.backlinks || []).map((link: any) => ({ urlFrom: link.url_from || '', urlTo: link.url_to || '', - httpCode: link.http_code ?? null, + httpCode: link.http_code_target ?? null, anchor: link.anchor || '', domainRatingSource: link.domain_rating_source ?? 0, })) diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts index a78f8e1c2a5..2c1a6f81ac2 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts @@ -99,7 +99,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< position: competitor.position ?? null, bestPositionKind: competitor.best_position_kind ?? null, traffic: competitor.traffic ?? null, - value: typeof competitor.value === 'number' ? competitor.value / 100 : null, + value: competitor.value ?? null, })), })) @@ -154,7 +154,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< }, value: { type: 'number', - description: 'Estimated traffic value (USD)', + description: 'Estimated traffic value', optional: true, }, }, diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts index d8b8ab39143..0a2c8d373ce 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts @@ -79,7 +79,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< 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, + trafficValue: item.traffic_value ?? null, averagePosition: item.average_position ?? null, pos1To3: item.pos_1_3 ?? 0, pos4To10: item.pos_4_10 ?? 0, @@ -110,7 +110,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< }, trafficValue: { type: 'number', - description: 'Estimated monthly organic traffic value (USD)', + description: 'Estimated monthly organic traffic value', optional: true, }, averagePosition: { diff --git a/apps/sim/tools/ahrefs/top_pages.ts b/apps/sim/tools/ahrefs/top_pages.ts index 9feb05c40b6..8c0b6e0f22a 100644 --- a/apps/sim/tools/ahrefs/top_pages.ts +++ b/apps/sim/tools/ahrefs/top_pages.ts @@ -28,7 +28,7 @@ export const topPagesTool: ToolConfig Date: Mon, 6 Jul 2026 15:30:57 -0700 Subject: [PATCH 5/8] fix(ahrefs): convert rank tracker competitor value/trafficValue to USD Both bots independently flagged these two fields as inconsistent with every other monetary field in the integration, all 7 of which are explicitly documented as USD cents. Neither field has explicit unit documentation (one is undocumented as cents, the other's schema isn't statically retrievable at all), but given the unanimous pattern across every other verified field and no contrary evidence, converting for consistency is the better bet than leaving them as an outlier. --- apps/docs/content/docs/en/integrations/ahrefs.mdx | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts | 4 ++-- apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index ca38e1dd6ea..0775e0b0ff8 100644 --- a/apps/docs/content/docs/en/integrations/ahrefs.mdx +++ b/apps/docs/content/docs/en/integrations/ahrefs.mdx @@ -633,7 +633,7 @@ Get competitor rankings for the keywords tracked in an Ahrefs Rank Tracker proje | ↳ `position` | number | Current ranking position | | ↳ `bestPositionKind` | string | Type of the best position achieved | | ↳ `traffic` | number | Estimated traffic to the competitor | -| ↳ `value` | number | Estimated traffic value | +| ↳ `value` | number | Estimated traffic value \(USD\) | ### `ahrefs_rank_tracker_competitors_stats` @@ -656,7 +656,7 @@ Get aggregate competitor stats for an Ahrefs Rank Tracker project: each competit | `competitorsStats` | array | Aggregate stats for each tracked competitor | | ↳ `competitor` | string | The competitor's URL | | ↳ `traffic` | number | Estimated monthly organic visits | -| ↳ `trafficValue` | number | Estimated monthly organic traffic value | +| ↳ `trafficValue` | number | Estimated monthly organic traffic value \(USD\) | | ↳ `averagePosition` | number | Average top organic position across tracked keywords | | ↳ `pos1To3` | number | Keywords ranking in top 3 positions | | ↳ `pos4To10` | number | Keywords ranking in positions 4-10 | diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts index 2c1a6f81ac2..a78f8e1c2a5 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_overview.ts @@ -99,7 +99,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< position: competitor.position ?? null, bestPositionKind: competitor.best_position_kind ?? null, traffic: competitor.traffic ?? null, - value: competitor.value ?? null, + value: typeof competitor.value === 'number' ? competitor.value / 100 : null, })), })) @@ -154,7 +154,7 @@ export const rankTrackerCompetitorsOverviewTool: ToolConfig< }, value: { type: 'number', - description: 'Estimated traffic value', + description: 'Estimated traffic value (USD)', optional: true, }, }, diff --git a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts index 0a2c8d373ce..d8b8ab39143 100644 --- a/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts +++ b/apps/sim/tools/ahrefs/rank_tracker_competitors_stats.ts @@ -79,7 +79,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< const competitorsStats = (data['competitors-metrics'] || []).map((item: any) => ({ competitor: item.competitor || '', traffic: item.traffic ?? null, - trafficValue: item.traffic_value ?? 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, @@ -110,7 +110,7 @@ export const rankTrackerCompetitorsStatsTool: ToolConfig< }, trafficValue: { type: 'number', - description: 'Estimated monthly organic traffic value', + description: 'Estimated monthly organic traffic value (USD)', optional: true, }, averagePosition: { From 8a745ba7abea29a14d3707f08b68e51a559f5025 Mon Sep 17 00:00:00 2001 From: waleed Date: Mon, 6 Jul 2026 15:39:00 -0700 Subject: [PATCH 6/8] style(ahrefs): drop non-TSDoc inline comments introduced in this PR Repo convention disallows non-TSDoc comments; removed the three explanatory // comments this PR added next to cents-to-USD conversions (keyword_overview, paid_pages, related_terms) - pre-existing comments elsewhere in the file are untouched, out of scope for this PR. --- apps/sim/tools/ahrefs/keyword_overview.ts | 1 - apps/sim/tools/ahrefs/paid_pages.ts | 1 - apps/sim/tools/ahrefs/related_terms.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/apps/sim/tools/ahrefs/keyword_overview.ts b/apps/sim/tools/ahrefs/keyword_overview.ts index 4b54c0f5292..3ee7c4142de 100644 --- a/apps/sim/tools/ahrefs/keyword_overview.ts +++ b/apps/sim/tools/ahrefs/keyword_overview.ts @@ -69,7 +69,6 @@ export const keywordOverviewTool: ToolConfig< keyword: result.keyword || '', searchVolume: result.volume ?? 0, keywordDifficulty: result.difficulty ?? null, - // Ahrefs returns cpc in USD cents; convert to USD to match the documented unit cpc: typeof result.cpc === 'number' ? result.cpc / 100 : null, clicks: result.clicks ?? null, clicksPercentage: result.searches_pct_clicks_organic_only ?? null, diff --git a/apps/sim/tools/ahrefs/paid_pages.ts b/apps/sim/tools/ahrefs/paid_pages.ts index 6aec0ec5e11..7f7760356aa 100644 --- a/apps/sim/tools/ahrefs/paid_pages.ts +++ b/apps/sim/tools/ahrefs/paid_pages.ts @@ -82,7 +82,6 @@ export const paidPagesTool: ToolConfig Date: Mon, 6 Jul 2026 15:46:28 -0700 Subject: [PATCH 7/8] fix(ahrefs): default optional country to us consistently across all tools paid_pages, metrics_history, keywords_history, and batch_analysis were the only 4 of the 11 tools with an optional country param that didn't fall back to "us" when omitted, unlike domain_rating, metrics, keyword_overview, organic_keywords, organic_competitors, top_pages, and related_terms - all of which default client-side. Aligned all four to the same convention so direct tool/agent calls without an explicit country get the same behavior regardless of which operation is used. --- apps/docs/content/docs/en/integrations/ahrefs.mdx | 8 ++++---- apps/sim/tools/ahrefs/batch_analysis.ts | 4 ++-- apps/sim/tools/ahrefs/keywords_history.ts | 4 ++-- apps/sim/tools/ahrefs/metrics_history.ts | 4 ++-- apps/sim/tools/ahrefs/paid_pages.ts | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index 0775e0b0ff8..329f4428fa1 100644 --- a/apps/docs/content/docs/en/integrations/ahrefs.mdx +++ b/apps/docs/content/docs/en/integrations/ahrefs.mdx @@ -302,7 +302,7 @@ Get a target domain's pages that receive paid search traffic, sorted by estimate | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `target` | string | Yes | The target domain or URL to analyze. Example: "example.com" | -| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" \(default: "us"\) | | `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | | `date` | string | No | Date to report metrics on, in YYYY-MM-DD format \(defaults to today\) | | `limit` | number | No | Maximum number of results to return. Example: 50 \(default: 1000\) | @@ -410,7 +410,7 @@ Get the historical organic and paid traffic trend for a target domain or URL ove | `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | | `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | | `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | -| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" \(default: "us"\) | | `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | | `apiKey` | string | Yes | Ahrefs API Key | @@ -460,7 +460,7 @@ Get the historical organic keyword ranking distribution for a target domain or U | `dateFrom` | string | Yes | Start date of the historical period, in YYYY-MM-DD format | | `dateTo` | string | No | End date of the historical period, in YYYY-MM-DD format \(defaults to today\) | | `historyGrouping` | string | No | Time interval for grouping data points: "daily", "weekly", or "monthly" \(default: "monthly"\) | -| `country` | string | No | Country code for search results. Example: "us", "gb", "de" | +| `country` | string | No | Country code for search results. Example: "us", "gb", "de" \(default: "us"\) | | `mode` | string | No | Analysis mode: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | | `apiKey` | string | Yes | Ahrefs API Key | @@ -487,7 +487,7 @@ Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traff | `targets` | string | Yes | Comma-separated list of domains or URLs to analyze. Example: "example.com,competitor.com" | | `mode` | string | No | Analysis mode applied to every target: domain \(entire domain\), prefix \(URL prefix\), subdomains \(include all subdomains, default\), exact \(exact URL match\) | | `protocol` | string | No | Protocol applied to every target: "both" \(default\), "http", or "https" | -| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" | +| `country` | string | No | Country code for traffic data. Example: "us", "gb", "de" \(default: "us"\) | | `volumeMode` | string | No | Search volume calculation: "monthly" or "average" \(default: "monthly"\) | | `apiKey` | string | Yes | Ahrefs API Key | diff --git a/apps/sim/tools/ahrefs/batch_analysis.ts b/apps/sim/tools/ahrefs/batch_analysis.ts index 99a99bbac5c..585f8fd95e8 100644 --- a/apps/sim/tools/ahrefs/batch_analysis.ts +++ b/apps/sim/tools/ahrefs/batch_analysis.ts @@ -37,7 +37,7 @@ export const batchAnalysisTool: ToolConfig Date: Mon, 6 Jul 2026 15:54:41 -0700 Subject: [PATCH 8/8] fix(ahrefs): split shared date subBlock id for datetime-format operations site_audit_page_explorer and rank_tracker_serp_overview both reused the generic 'date' subBlock id with YYYY-MM-DDThh:mm:ss semantics, while every other operation using that same id expects YYYY-MM-DD. Switching operations without clearing the field could carry a stale wrong-format value into the new operation's request. Split into distinct ids (crawlDate, asOfDate) mapped back to each tool's date param in tools.config.params. --- apps/sim/blocks/blocks/ahrefs.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/sim/blocks/blocks/ahrefs.ts b/apps/sim/blocks/blocks/ahrefs.ts index 8a0001b8a51..b27de4a0d66 100644 --- a/apps/sim/blocks/blocks/ahrefs.ts +++ b/apps/sim/blocks/blocks/ahrefs.ts @@ -855,7 +855,7 @@ export const AhrefsBlock: BlockConfig = { mode: 'advanced', }, { - id: 'date', + id: 'crawlDate', title: 'Crawl Date', type: 'short-input', placeholder: 'YYYY-MM-DDThh:mm:ss (defaults to most recent crawl)', @@ -976,7 +976,7 @@ export const AhrefsBlock: BlockConfig = { mode: 'advanced', }, { - id: 'date', + id: 'asOfDate', title: 'As Of', type: 'short-input', placeholder: 'YYYY-MM-DDThh:mm:ss (defaults to latest)', @@ -1185,6 +1185,8 @@ export const AhrefsBlock: BlockConfig = { if (params.topPositions) result.topPositions = Number(params.topPositions) if (params.locationId) result.locationId = Number(params.locationId) if (params.offset) result.offset = Number(params.offset) + if (params.crawlDate) result.date = params.crawlDate + if (params.asOfDate) result.date = params.asOfDate return result }, }, @@ -1233,6 +1235,14 @@ export const AhrefsBlock: BlockConfig = { }, issueId: { type: 'string', description: 'Site Audit issue ID to filter affected pages' }, offset: { type: 'number', description: 'Number of results to skip, for pagination' }, + crawlDate: { + type: 'string', + description: 'Site Audit crawl date/time in YYYY-MM-DDThh:mm:ss format', + }, + asOfDate: { + type: 'string', + description: 'Rank Tracker SERP snapshot date/time in YYYY-MM-DDThh:mm:ss format', + }, }, outputs: { // Domain Rating output