diff --git a/apps/docs/content/docs/en/integrations/ahrefs.mdx b/apps/docs/content/docs/en/integrations/ahrefs.mdx index 92d278e9fab..329f4428fa1 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 */} @@ -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 | @@ -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" \(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\) | +| `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" \(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 | + +#### 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" \(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 | + +#### 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" \(default: "us"\) | +| `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 \(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 | + +### `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 \(USD\) | + +### `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 \(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 | +| ↳ `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..b27de4a0d66 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: 'crawlDate', + 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: 'asOfDate', + 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,12 @@ 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) + if (params.crawlDate) result.date = params.crawlDate + if (params.asOfDate) result.date = params.asOfDate return result }, }, @@ -480,6 +1204,45 @@ 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' }, + 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 @@ -515,6 +1278,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 +1416,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..585f8fd95e8 --- /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" (default: "us")', + }, + volumeMode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Search volume calculation: "monthly" or "average" (default: "monthly")', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ahrefs API Key', + }, + }, + + request: { + url: () => 'https://api.ahrefs.com/v3/batch-analysis/batch-analysis', + method: 'POST', + headers: (params) => ({ + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + body: (params) => { + const targets = params.targets + .split(',') + .map((target) => target.trim()) + .filter((target) => target.length > 0) + .map((url) => ({ + url, + mode: params.mode || 'subdomains', + protocol: params.protocol || 'both', + })) + + return { + select: SELECT_FIELDS.split(','), + targets, + country: params.country || 'us', + volume_mode: params.volumeMode || 'monthly', + } + }, + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || data.error || 'Failed to run batch analysis') + } + + const results = (data.targets || []).map((item: any) => ({ + url: item.url || '', + index: item.index ?? 0, + domainRating: item.domain_rating ?? null, + ahrefsRank: item.ahrefs_rank ?? null, + backlinks: item.backlinks ?? null, + referringDomains: item.refdomains ?? null, + organicTraffic: item.org_traffic ?? null, + organicKeywords: item.org_keywords ?? null, + paidTraffic: item.paid_traffic ?? null, + error: item.error ?? null, + })) + + return { + success: true, + output: { + results, + }, + } + }, + + outputs: { + results: { + type: 'array', + description: 'Bulk metrics for each analyzed target, in submission order', + items: { + type: 'object', + properties: { + url: { type: 'string', description: 'The analyzed target URL or domain' }, + index: { type: 'number', description: 'Index of the target in the submitted list' }, + domainRating: { + type: 'number', + description: 'Domain Rating score (0-100)', + optional: true, + }, + ahrefsRank: { + type: 'number', + description: 'Ahrefs Rank (global ranking)', + optional: true, + }, + backlinks: { + type: 'number', + description: 'Total backlinks to the target', + optional: true, + }, + referringDomains: { + type: 'number', + description: 'Unique domains linking to the target', + optional: true, + }, + organicTraffic: { + type: 'number', + description: 'Estimated monthly organic traffic', + optional: true, + }, + organicKeywords: { + type: 'number', + description: 'Number of organic keywords ranked (top 100)', + optional: true, + }, + paidTraffic: { + type: 'number', + description: 'Estimated monthly paid search traffic', + optional: true, + }, + error: { + type: 'string', + description: 'Error message if this target could not be analyzed', + optional: true, + }, + }, + }, + }, + }, + } 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..3ee7c4142de 100644 --- a/apps/sim/tools/ahrefs/keyword_overview.ts +++ b/apps/sim/tools/ahrefs/keyword_overview.ts @@ -69,7 +69,7 @@ export const keywordOverviewTool: ToolConfig< keyword: result.keyword || '', searchVolume: result.volume ?? 0, keywordDifficulty: result.difficulty ?? null, - cpc: result.cpc ?? null, + cpc: typeof result.cpc === 'number' ? result.cpc / 100 : null, clicks: result.clicks ?? null, clicksPercentage: result.searches_pct_clicks_organic_only ?? null, parentTopic: result.parent_topic ?? null, diff --git a/apps/sim/tools/ahrefs/keywords_history.ts b/apps/sim/tools/ahrefs/keywords_history.ts new file mode 100644 index 00000000000..b74c852975f --- /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" (default: "us")', + }, + mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ahrefs API Key', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.ahrefs.com/v3/site-explorer/keywords-history') + url.searchParams.set('target', params.target) + url.searchParams.set('date_from', params.dateFrom) + url.searchParams.set('select', SELECT_FIELDS) + if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping) + if (params.dateTo) url.searchParams.set('date_to', params.dateTo) + url.searchParams.set('country', params.country || 'us') + if (params.mode) url.searchParams.set('mode', params.mode) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || data.error || 'Failed to get keywords history') + } + + const keywordsHistory = (data.keywords || []).map((item: any) => ({ + date: item.date || '', + top3: item.top3 ?? 0, + top4To10: item.top4_10 ?? 0, + top11To20: item.top11_20 ?? 0, + top21To50: item.top21_50 ?? 0, + top51Plus: item.top51_plus ?? 0, + })) + + return { + success: true, + output: { + keywordsHistory, + }, + } + }, + + outputs: { + keywordsHistory: { + type: 'array', + description: 'Historical organic keyword ranking distribution', + items: { + type: 'object', + properties: { + date: { type: 'string', description: 'Date of the record' }, + top3: { type: 'number', description: 'Keywords ranking in top 3 organic results' }, + top4To10: { type: 'number', description: 'Keywords ranking in positions 4-10' }, + top11To20: { type: 'number', description: 'Keywords ranking in positions 11-20' }, + top21To50: { type: 'number', description: 'Keywords ranking in positions 21-50' }, + top51Plus: { type: 'number', description: 'Keywords ranking in position 51 and beyond' }, + }, + }, + }, + }, +} 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..1e30e4f6922 --- /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" (default: "us")', + }, + mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ahrefs API Key', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.ahrefs.com/v3/site-explorer/metrics-history') + url.searchParams.set('target', params.target) + url.searchParams.set('date_from', params.dateFrom) + if (params.dateTo) url.searchParams.set('date_to', params.dateTo) + url.searchParams.set('select', SELECT_FIELDS) + url.searchParams.set('volume_mode', params.volumeMode || 'monthly') + if (params.historyGrouping) url.searchParams.set('history_grouping', params.historyGrouping) + url.searchParams.set('country', params.country || 'us') + if (params.mode) url.searchParams.set('mode', params.mode) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || data.error || 'Failed to get metrics history') + } + + const metrics = (data.metrics || []).map((item: any) => ({ + date: item.date || '', + organicTraffic: item.org_traffic ?? 0, + organicCost: typeof item.org_cost === 'number' ? item.org_cost / 100 : null, + paidTraffic: item.paid_traffic ?? 0, + paidCost: typeof item.paid_cost === 'number' ? item.paid_cost / 100 : null, + })) + + return { + success: true, + output: { + metricsHistory: metrics, + }, + } + }, + + outputs: { + metricsHistory: { + type: 'array', + description: 'Historical organic and paid traffic data points', + items: { + type: 'object', + properties: { + date: { type: 'string', description: 'Date of the metric entry' }, + organicTraffic: { type: 'number', description: 'Estimated monthly organic visits' }, + organicCost: { + type: 'number', + description: 'Estimated monthly cost to replicate organic traffic via ads (USD)', + optional: true, + }, + paidTraffic: { type: 'number', description: 'Estimated monthly paid search visits' }, + paidCost: { + type: 'number', + description: 'Estimated monthly paid search spend (USD)', + optional: true, + }, + }, + }, + }, + }, +} diff --git a/apps/sim/tools/ahrefs/paid_pages.ts b/apps/sim/tools/ahrefs/paid_pages.ts new file mode 100644 index 00000000000..1ab8001ee9f --- /dev/null +++ b/apps/sim/tools/ahrefs/paid_pages.ts @@ -0,0 +1,134 @@ +import type { AhrefsPaidPagesParams, AhrefsPaidPagesResponse } from '@/tools/ahrefs/types' +import type { ToolConfig } from '@/tools/types' + +const SELECT_FIELDS = 'url,sum_traffic,keywords,top_keyword,value,ads_count' + +export const paidPagesTool: ToolConfig = { + id: 'ahrefs_paid_pages', + name: 'Ahrefs Paid Pages', + description: + "Get a target domain's pages that receive paid search traffic, sorted by estimated paid traffic. Returns page URLs with their paid traffic, keyword counts, and estimated spend.", + version: '1.0.0', + + params: { + target: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The target domain or URL to analyze. Example: "example.com"', + }, + country: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")', + }, + mode: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)', + }, + date: { + type: 'string', + required: false, + visibility: 'user-only', + description: 'Date to report metrics on, in YYYY-MM-DD format (defaults to today)', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return. Example: 50 (default: 1000)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ahrefs API Key', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.ahrefs.com/v3/site-explorer/paid-pages') + url.searchParams.set('target', params.target) + url.searchParams.set('select', SELECT_FIELDS) + // Date is required - default to today if not provided + const date = params.date || new Date().toISOString().split('T')[0] + url.searchParams.set('date', date) + url.searchParams.set('country', params.country || 'us') + if (params.mode) url.searchParams.set('mode', params.mode) + if (params.limit) url.searchParams.set('limit', String(params.limit)) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || data.error || 'Failed to get paid pages') + } + + const paidPages = (data.pages || []).map((page: any) => ({ + url: page.url ?? null, + traffic: page.sum_traffic ?? null, + keywords: page.keywords ?? null, + topKeyword: page.top_keyword ?? null, + value: typeof page.value === 'number' ? page.value / 100 : null, + adsCount: page.ads_count ?? null, + })) + + return { + success: true, + output: { + paidPages, + }, + } + }, + + outputs: { + paidPages: { + type: 'array', + description: 'List of pages receiving paid search traffic', + items: { + type: 'object', + properties: { + url: { type: 'string', description: 'The page URL', optional: true }, + traffic: { + type: 'number', + description: 'Estimated monthly paid search traffic', + optional: true, + }, + keywords: { + type: 'number', + description: 'Number of paid keywords the page ranks for', + optional: true, + }, + topKeyword: { + type: 'string', + description: 'The top keyword driving paid traffic to this page', + optional: true, + }, + value: { + type: 'number', + description: 'Estimated monthly paid traffic cost in USD', + optional: true, + }, + adsCount: { + type: 'number', + description: 'Number of unique ads shown for this page', + optional: true, + }, + }, + }, + }, + }, +} 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..a78f8e1c2a5 --- /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: typeof competitor.value === 'number' ? competitor.value / 100 : null, + })), + })) + + return { + success: true, + output: { + competitorKeywords, + }, + } + }, + + outputs: { + competitorKeywords: { + type: 'array', + description: 'Tracked keywords with competitor ranking data', + items: { + type: 'object', + properties: { + keyword: { type: 'string', description: 'The tracked keyword' }, + volume: { type: 'number', description: 'Average monthly search volume', optional: true }, + keywordDifficulty: { + type: 'number', + description: 'Keyword difficulty score (0-100)', + optional: true, + }, + serpFeatures: { + type: 'array', + description: 'SERP features present in the results', + items: { type: 'string' }, + }, + competitorsList: { + type: 'array', + description: 'Ranking data for each tracked competitor on this keyword', + items: { + type: 'object', + properties: { + url: { type: 'string', description: "The competitor's ranking URL" }, + position: { + type: 'number', + description: 'Current ranking position', + optional: true, + }, + bestPositionKind: { + type: 'string', + description: 'Type of the best position achieved', + optional: true, + }, + traffic: { + type: 'number', + description: 'Estimated traffic to the competitor', + optional: true, + }, + value: { + type: 'number', + description: 'Estimated traffic value (USD)', + optional: true, + }, + }, + }, + }, + }, + }, + }, + }, +} 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..d8b8ab39143 --- /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: typeof item.traffic_value === 'number' ? item.traffic_value / 100 : null, + averagePosition: item.average_position ?? null, + pos1To3: item.pos_1_3 ?? 0, + pos4To10: item.pos_4_10 ?? 0, + shareOfVoice: item.share_of_voice ?? 0, + shareOfTrafficValue: item.share_of_traffic_value ?? 0, + })) + + return { + success: true, + output: { + competitorsStats, + }, + } + }, + + outputs: { + competitorsStats: { + type: 'array', + description: 'Aggregate stats for each tracked competitor', + items: { + type: 'object', + properties: { + competitor: { type: 'string', description: "The competitor's URL" }, + traffic: { + type: 'number', + description: 'Estimated monthly organic visits', + optional: true, + }, + trafficValue: { + type: 'number', + description: 'Estimated monthly organic traffic value (USD)', + optional: true, + }, + averagePosition: { + type: 'number', + description: 'Average top organic position across tracked keywords', + optional: true, + }, + pos1To3: { type: 'number', description: 'Keywords ranking in top 3 positions' }, + pos4To10: { type: 'number', description: 'Keywords ranking in positions 4-10' }, + shareOfVoice: { type: 'number', description: 'Organic traffic share percentage' }, + shareOfTrafficValue: { + type: 'number', + description: 'Organic traffic value share percentage', + }, + }, + }, + }, + }, +} 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..a1b72f7de04 --- /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: typeof item.value === 'number' ? item.value / 100 : null, + topKeyword: item.top_keyword ?? null, + topKeywordVolume: item.top_keyword_volume ?? null, + updateDate: item.update_date || '', + })) + + return { + success: true, + output: { + positions, + }, + } + }, + + outputs: { + positions: { + type: 'array', + description: 'Every ranking result on the SERP for the tracked keyword', + items: { + type: 'object', + properties: { + position: { type: 'number', description: 'Position of the result in the SERP' }, + url: { type: 'string', description: 'URL of the ranking page' }, + title: { type: 'string', description: 'Page title' }, + type: { + type: 'array', + description: 'The kind of the position: organic, paid, or a SERP feature', + items: { type: 'string' }, + }, + domainRating: { type: 'number', description: 'Domain Rating of the ranking domain' }, + urlRating: { type: 'number', description: 'URL Rating of the ranking page' }, + backlinks: { type: 'number', description: 'Total backlinks to the ranking domain' }, + refdomains: { type: 'number', description: 'Unique referring domains' }, + traffic: { type: 'number', description: 'Estimated monthly organic search traffic' }, + value: { + type: 'number', + description: 'Estimated monthly traffic value (USD)', + optional: true, + }, + topKeyword: { + type: 'string', + description: 'Highest-traffic keyword ranking for this page', + optional: true, + }, + topKeywordVolume: { + type: 'number', + description: 'Monthly search volume for the top keyword', + optional: true, + }, + updateDate: { type: 'string', description: 'Date the SERP was last checked' }, + }, + }, + }, + }, +} 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..3b5c7626ec3 --- /dev/null +++ b/apps/sim/tools/ahrefs/related_terms.ts @@ -0,0 +1,139 @@ +import type { AhrefsRelatedTermsParams, AhrefsRelatedTermsResponse } from '@/tools/ahrefs/types' +import type { ToolConfig } from '@/tools/types' + +const SELECT_FIELDS = + 'keyword,volume,difficulty,cpc,parent_topic,traffic_potential,intents,serp_features' + +export const relatedTermsTool: ToolConfig = { + id: 'ahrefs_related_terms', + name: 'Ahrefs Related Terms', + description: + 'Get keyword ideas related to a seed keyword: terms the same top-ranking pages also rank for ("also rank for") or also discuss ("also talk about"), with volume, difficulty, and CPC.', + version: '1.0.0', + + params: { + keyword: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The seed keyword to find related terms for', + }, + country: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Country code for keyword data. Example: "us", "gb", "de" (default: "us")', + }, + terms: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Type of related keywords to return: "also_rank_for", "also_talk_about", or "all" (default: "all")', + }, + viewFor: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: + 'Whether to derive related terms from the top 10 or top 100 ranking pages (default: "top_10")', + }, + limit: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Maximum number of results to return. Example: 50 (default: 1000)', + }, + apiKey: { + type: 'string', + required: true, + visibility: 'user-only', + description: 'Ahrefs API Key', + }, + }, + + request: { + url: (params) => { + const url = new URL('https://api.ahrefs.com/v3/keywords-explorer/related-terms') + url.searchParams.set('select', SELECT_FIELDS) + url.searchParams.set('country', params.country || 'us') + url.searchParams.set('keywords', params.keyword) + if (params.terms) url.searchParams.set('terms', params.terms) + if (params.viewFor) url.searchParams.set('view_for', params.viewFor) + if (params.limit) url.searchParams.set('limit', String(params.limit)) + return url.toString() + }, + method: 'GET', + headers: (params) => ({ + Accept: 'application/json', + Authorization: `Bearer ${params.apiKey}`, + }), + }, + + transformResponse: async (response: Response) => { + const data = await response.json() + + if (!response.ok) { + throw new Error(data.error?.message || data.error || 'Failed to get related terms') + } + + const relatedTerms = (data.keywords || []).map((item: any) => ({ + keyword: item.keyword || '', + volume: item.volume ?? null, + keywordDifficulty: item.difficulty ?? null, + cpc: typeof item.cpc === 'number' ? item.cpc / 100 : null, + parentTopic: item.parent_topic ?? null, + trafficPotential: item.traffic_potential ?? null, + intents: item.intents ?? null, + serpFeatures: item.serp_features ?? [], + })) + + return { + success: true, + output: { + relatedTerms, + }, + } + }, + + outputs: { + relatedTerms: { + type: 'array', + description: 'Related keyword ideas for the seed keyword', + items: { + type: 'object', + properties: { + keyword: { type: 'string', description: 'The related keyword' }, + volume: { type: 'number', description: 'Average monthly search volume', optional: true }, + keywordDifficulty: { + type: 'number', + description: 'Keyword difficulty score (0-100)', + optional: true, + }, + cpc: { type: 'number', description: 'Cost per click in USD', optional: true }, + parentTopic: { + type: 'string', + description: 'The parent topic for this keyword', + optional: true, + }, + trafficPotential: { + type: 'number', + description: 'Estimated traffic potential if ranking #1', + optional: true, + }, + intents: { + type: 'object', + description: + 'Search intent flags (informational, navigational, commercial, transactional, branded, local)', + optional: true, + }, + serpFeatures: { + type: 'array', + description: 'SERP features present in the results', + items: { type: 'string' }, + }, + }, + }, + }, + }, +} 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/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 | 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,