Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 3.8k
feat(ahrefs): validate integration, fix cents/column bugs, add 13 v3 endpoints#5447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
934dca7dab60b93c3d8bfe4dcffcea0e1a48a745ba48fbdfdb1b5611File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
Large diffs are not rendered by default.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| import type { AhrefsAnchorsParams, AhrefsAnchorsResponse } from '@/tools/ahrefs/types' | ||
| import type { ToolConfig } from '@/tools/types' | ||
| const SELECT_FIELDS = 'anchor,links_to_target,dofollow_links,refdomains,first_seen,last_seen' | ||
| export const anchorsTool: ToolConfig<AhrefsAnchorsParams, AhrefsAnchorsResponse> = { | ||
| id: 'ahrefs_anchors', | ||
| name: 'Ahrefs Anchors', | ||
| description: | ||
| "Get the anchor text distribution for a target domain or URL's backlinks, showing how many links and referring domains use each anchor text.", | ||
| version: '1.0.0', | ||
| params: { | ||
| target: { | ||
| type: 'string', | ||
| required: true, | ||
| visibility: 'user-or-llm', | ||
| description: | ||
| 'The target domain or URL to analyze. Example: "example.com" or "https://example.com/page"', | ||
| }, | ||
| mode: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: | ||
| 'Analysis mode: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)', | ||
| }, | ||
| history: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: | ||
| 'Historical scope: "live" (currently live), "all_time" (default, includes lost backlinks), or "since:YYYY-MM-DD" (backlinks found since a date)', | ||
| }, | ||
| limit: { | ||
| type: 'number', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: 'Maximum number of results to return. Example: 50 (default: 1000)', | ||
| }, | ||
| apiKey: { | ||
| type: 'string', | ||
| required: true, | ||
| visibility: 'user-only', | ||
| description: 'Ahrefs API Key', | ||
| }, | ||
| }, | ||
| request: { | ||
| url: (params) => { | ||
| const url = new URL('https://api.ahrefs.com/v3/site-explorer/anchors') | ||
| url.searchParams.set('target', params.target) | ||
| url.searchParams.set('select', SELECT_FIELDS) | ||
| if (params.mode) url.searchParams.set('mode', params.mode) | ||
| url.searchParams.set('history', params.history || 'all_time') | ||
| if (params.limit) url.searchParams.set('limit', String(params.limit)) | ||
| return url.toString() | ||
| }, | ||
| method: 'GET', | ||
| headers: (params) => ({ | ||
| Accept: 'application/json', | ||
| Authorization: `Bearer ${params.apiKey}`, | ||
| }), | ||
| }, | ||
| transformResponse: async (response: Response) => { | ||
| const data = await response.json() | ||
| if (!response.ok) { | ||
| throw new Error(data.error?.message || data.error || 'Failed to get anchors') | ||
| } | ||
| const anchors = (data.anchors || []).map((item: any) => ({ | ||
| anchor: item.anchor || '', | ||
| backlinks: item.links_to_target ?? 0, | ||
| dofollowBacklinks: item.dofollow_links ?? 0, | ||
| referringDomains: item.refdomains ?? 0, | ||
| firstSeen: item.first_seen || '', | ||
| lastSeen: item.last_seen ?? null, | ||
| })) | ||
| return { | ||
| success: true, | ||
| output: { | ||
| anchors, | ||
| }, | ||
| } | ||
| }, | ||
| outputs: { | ||
| anchors: { | ||
| type: 'array', | ||
| description: 'Anchor text distribution for the backlink profile', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| anchor: { type: 'string', description: 'The anchor text' }, | ||
| backlinks: { type: 'number', description: 'Total backlinks using this anchor text' }, | ||
| dofollowBacklinks: { | ||
| type: 'number', | ||
| description: 'Number of dofollow backlinks using this anchor text', | ||
| }, | ||
| referringDomains: { | ||
| type: 'number', | ||
| description: 'Number of unique referring domains using this anchor text', | ||
| }, | ||
| firstSeen: { | ||
| type: 'string', | ||
| description: 'When a link with this anchor was first found', | ||
| }, | ||
| lastSeen: { | ||
| type: 'string', | ||
| description: 'When a backlink with this anchor was last seen (null if still live)', | ||
| optional: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| import type { AhrefsBatchAnalysisParams, AhrefsBatchAnalysisResponse } from '@/tools/ahrefs/types' | ||
| import type { ToolConfig } from '@/tools/types' | ||
| const SELECT_FIELDS = | ||
| 'url,domain_rating,ahrefs_rank,backlinks,refdomains,org_traffic,org_keywords,paid_traffic' | ||
| export const batchAnalysisTool: ToolConfig<AhrefsBatchAnalysisParams, AhrefsBatchAnalysisResponse> = | ||
| { | ||
| id: 'ahrefs_batch_analysis', | ||
| name: 'Ahrefs Batch Analysis', | ||
| description: | ||
| 'Get bulk SEO metrics (Domain Rating, backlinks, referring domains, organic traffic, and more) for multiple domains or URLs in a single request. Useful for comparing many competitors at once.', | ||
| version: '1.0.0', | ||
| params: { | ||
| targets: { | ||
| type: 'string', | ||
| required: true, | ||
| visibility: 'user-or-llm', | ||
| description: | ||
| 'Comma-separated list of domains or URLs to analyze. Example: "example.com,competitor.com"', | ||
| }, | ||
| mode: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: | ||
| 'Analysis mode applied to every target: domain (entire domain), prefix (URL prefix), subdomains (include all subdomains, default), exact (exact URL match)', | ||
| }, | ||
| protocol: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: 'Protocol applied to every target: "both" (default), "http", or "https"', | ||
| }, | ||
| country: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: 'Country code for traffic data. Example: "us", "gb", "de" (default: "us")', | ||
| }, | ||
| volumeMode: { | ||
| type: 'string', | ||
| required: false, | ||
| visibility: 'user-or-llm', | ||
| description: 'Search volume calculation: "monthly" or "average" (default: "monthly")', | ||
| }, | ||
| apiKey: { | ||
| type: 'string', | ||
| required: true, | ||
| visibility: 'user-only', | ||
| description: 'Ahrefs API Key', | ||
| }, | ||
| }, | ||
| request: { | ||
| url: () => 'https://api.ahrefs.com/v3/batch-analysis/batch-analysis', | ||
| method: 'POST', | ||
| headers: (params) => ({ | ||
| Accept: 'application/json', | ||
| 'Content-Type': 'application/json', | ||
| Authorization: `Bearer ${params.apiKey}`, | ||
| }), | ||
| body: (params) => { | ||
| const targets = params.targets | ||
| .split(',') | ||
| .map((target) => target.trim()) | ||
| .filter((target) => target.length > 0) | ||
| .map((url) => ({ | ||
| url, | ||
| mode: params.mode || 'subdomains', | ||
| protocol: params.protocol || 'both', | ||
| })) | ||
| return { | ||
| select: SELECT_FIELDS.split(','), | ||
| targets, | ||
| country: params.country || 'us', | ||
| volume_mode: params.volumeMode || 'monthly', | ||
| } | ||
| }, | ||
| }, | ||
| transformResponse: async (response: Response) => { | ||
| const data = await response.json() | ||
| if (!response.ok) { | ||
| throw new Error(data.error?.message || data.error || 'Failed to run batch analysis') | ||
| } | ||
| const results = (data.targets || []).map((item: any) => ({ | ||
| url: item.url || '', | ||
| index: item.index ?? 0, | ||
| domainRating: item.domain_rating ?? null, | ||
| ahrefsRank: item.ahrefs_rank ?? null, | ||
| backlinks: item.backlinks ?? null, | ||
| referringDomains: item.refdomains ?? null, | ||
| organicTraffic: item.org_traffic ?? null, | ||
| organicKeywords: item.org_keywords ?? null, | ||
| paidTraffic: item.paid_traffic ?? null, | ||
| error: item.error ?? null, | ||
| })) | ||
| return { | ||
| success: true, | ||
| output: { | ||
| results, | ||
| }, | ||
| } | ||
| }, | ||
| outputs: { | ||
| results: { | ||
| type: 'array', | ||
| description: 'Bulk metrics for each analyzed target, in submission order', | ||
| items: { | ||
| type: 'object', | ||
| properties: { | ||
| url: { type: 'string', description: 'The analyzed target URL or domain' }, | ||
| index: { type: 'number', description: 'Index of the target in the submitted list' }, | ||
| domainRating: { | ||
| type: 'number', | ||
| description: 'Domain Rating score (0-100)', | ||
| optional: true, | ||
| }, | ||
| ahrefsRank: { | ||
| type: 'number', | ||
| description: 'Ahrefs Rank (global ranking)', | ||
| optional: true, | ||
| }, | ||
| backlinks: { | ||
| type: 'number', | ||
| description: 'Total backlinks to the target', | ||
| optional: true, | ||
| }, | ||
| referringDomains: { | ||
| type: 'number', | ||
| description: 'Unique domains linking to the target', | ||
| optional: true, | ||
| }, | ||
| organicTraffic: { | ||
| type: 'number', | ||
| description: 'Estimated monthly organic traffic', | ||
| optional: true, | ||
| }, | ||
| organicKeywords: { | ||
| type: 'number', | ||
| description: 'Number of organic keywords ranked (top 100)', | ||
| optional: true, | ||
| }, | ||
| paidTraffic: { | ||
| type: 'number', | ||
| description: 'Estimated monthly paid search traffic', | ||
| optional: true, | ||
| }, | ||
| error: { | ||
| type: 'string', | ||
| description: 'Error message if this target could not be analyzed', | ||
| optional: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.