🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@power-seo/integrations

Package Overview
Dependencies
Maintainers
1
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@power-seo/integrations - npm Package Compare versions

Comparing version
1.0.15
to
1.0.16
+16
-39
dist/index.cjs

@@ -85,42 +85,19 @@ "use strict";

(0, import_core.consumeToken)(bucket);
let lastError = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const controller = new globalThis.AbortController();
const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await globalThis.fetch(url, {
...init,
signal: controller.signal
});
globalThis.clearTimeout(timeout);
if (response.ok) {
return await response.json();
return (0, import_core.fetchJsonWithRetry)({
maxRetries,
doFetch: async () => {
const controller = new globalThis.AbortController();
const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);
try {
return await globalThis.fetch(url, {
...init,
signal: controller.signal
});
} finally {
globalThis.clearTimeout(timeout);
}
const text = await response.text();
const error = new IntegrationApiError(
`${provider} API error: ${response.status} ${text}`,
response.status,
provider
);
if (!error.retryable || attempt === maxRetries) {
throw error;
}
lastError = error;
const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
await (0, import_core.sleep)(backoff);
} catch (err) {
globalThis.clearTimeout(timeout);
if (err instanceof IntegrationApiError) {
if (!err.retryable || attempt === maxRetries) {
throw err;
}
lastError = err;
const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
await (0, import_core.sleep)(backoff);
} else {
throw err;
}
}
}
throw lastError ?? new IntegrationApiError("Request failed", 500, provider);
},
buildError: (status, snippet) => new IntegrationApiError(`${provider} API error: ${status} ${snippet}`, status, provider),
fallbackError: () => new IntegrationApiError("Request failed", 500, provider)
});
}

@@ -127,0 +104,0 @@ return {

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/http.ts","../src/semrush/endpoints.ts","../src/semrush/client.ts","../src/ahrefs/endpoints.ts","../src/ahrefs/client.ts"],"sourcesContent":["// @power-seo/integrations — Main Entry Point\n// ----------------------------------------------------------------------------\n\n// Types\nexport type {\n AuthStrategy,\n HttpClientConfig,\n HttpClient,\n PaginatedResponse,\n SemrushDomainOverview,\n SemrushKeywordData,\n SemrushBacklinkData,\n SemrushKeywordDifficulty,\n SemrushRelatedKeyword,\n SemrushClient,\n SemrushPaginationOptions,\n AhrefsSiteOverview,\n AhrefsOrganicKeyword,\n AhrefsBacklink,\n AhrefsKeywordDifficulty,\n AhrefsReferringDomain,\n AhrefsClient,\n AhrefsPaginationOptions,\n} from './types.js';\n\n// Error class\nexport { IntegrationApiError } from './types.js';\n\n// HTTP Client\nexport { createHttpClient } from './http.js';\n\n// Semrush\nexport { createSemrushClient } from './semrush/client.js';\n\n// Ahrefs\nexport { createAhrefsClient } from './ahrefs/client.js';\n","// @power-seo/integrations — Types\n// ----------------------------------------------------------------------------\n\n// --- HTTP Client Types ---\n\nexport type AuthStrategy =\n | { type: 'query'; paramName: string; value: string }\n | { type: 'bearer'; token: string };\n\nexport interface HttpClientConfig {\n baseUrl: string;\n auth: AuthStrategy;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n timeoutMs?: number;\n}\n\nexport interface HttpClient {\n get: <T>(path: string, params?: Record<string, string>) => Promise<T>;\n post: <T>(path: string, body?: unknown) => Promise<T>;\n}\n\n// --- Error ---\n\nexport class IntegrationApiError extends Error {\n readonly status: number;\n readonly provider: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, provider: string) {\n super(message);\n this.name = 'IntegrationApiError';\n this.status = status;\n this.provider = provider;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Pagination ---\n\nexport interface PaginatedResponse<T> {\n data: T[];\n total: number;\n offset: number;\n limit: number;\n hasMore: boolean;\n}\n\n// --- Semrush Types ---\n\nexport interface SemrushDomainOverview {\n domain: string;\n organicKeywords: number;\n organicTraffic: number;\n organicCost: number;\n paidKeywords: number;\n paidTraffic: number;\n paidCost: number;\n backlinks: number;\n authorityScore: number;\n}\n\nexport interface SemrushKeywordData {\n keyword: string;\n position: number;\n previousPosition: number;\n searchVolume: number;\n cpc: number;\n url: string;\n traffic: number;\n trafficPercent: number;\n trafficCost: number;\n competition: number;\n}\n\nexport interface SemrushBacklinkData {\n sourceUrl: string;\n targetUrl: string;\n anchorText: string;\n isNoFollow: boolean;\n firstSeen: string;\n lastSeen: string;\n sourceAuthorityScore: number;\n}\n\nexport interface SemrushKeywordDifficulty {\n keyword: string;\n difficulty: number;\n searchVolume: number;\n cpc: number;\n competition: number;\n results: number;\n}\n\nexport interface SemrushRelatedKeyword {\n keyword: string;\n searchVolume: number;\n cpc: number;\n competition: number;\n results: number;\n relatedTo: string;\n}\n\nexport interface SemrushClient {\n getDomainOverview: (domain: string, database?: string) => Promise<SemrushDomainOverview>;\n getOrganicKeywords: (\n domain: string,\n options?: SemrushPaginationOptions,\n ) => Promise<PaginatedResponse<SemrushKeywordData>>;\n getBacklinks: (\n domain: string,\n options?: SemrushPaginationOptions,\n ) => Promise<PaginatedResponse<SemrushBacklinkData>>;\n getKeywordDifficulty: (\n keywords: string[],\n database?: string,\n ) => Promise<SemrushKeywordDifficulty[]>;\n getRelatedKeywords: (keyword: string, database?: string) => Promise<SemrushRelatedKeyword[]>;\n}\n\nexport interface SemrushPaginationOptions {\n limit?: number;\n offset?: number;\n database?: string;\n}\n\n// --- Ahrefs Types ---\n\nexport interface AhrefsSiteOverview {\n domain: string;\n domainRating: number;\n urlRating: number;\n backlinks: number;\n referringDomains: number;\n organicKeywords: number;\n organicTraffic: number;\n trafficValue: number;\n}\n\nexport interface AhrefsOrganicKeyword {\n keyword: string;\n position: number;\n searchVolume: number;\n cpc: number;\n url: string;\n traffic: number;\n trafficPercent: number;\n difficulty: number;\n}\n\nexport interface AhrefsBacklink {\n sourceUrl: string;\n targetUrl: string;\n anchorText: string;\n domainRating: number;\n urlRating: number;\n isDoFollow: boolean;\n firstSeen: string;\n lastSeen: string;\n}\n\nexport interface AhrefsKeywordDifficulty {\n keyword: string;\n difficulty: number;\n searchVolume: number;\n cpc: number;\n clicks: number;\n globalVolume: number;\n}\n\nexport interface AhrefsReferringDomain {\n domain: string;\n domainRating: number;\n backlinks: number;\n firstSeen: string;\n lastSeen: string;\n isDoFollow: boolean;\n}\n\nexport interface AhrefsClient {\n getSiteOverview: (domain: string) => Promise<AhrefsSiteOverview>;\n getOrganicKeywords: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsOrganicKeyword>>;\n getBacklinks: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsBacklink>>;\n getKeywordDifficulty: (keywords: string[]) => Promise<AhrefsKeywordDifficulty[]>;\n getReferringDomains: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsReferringDomain>>;\n}\n\nexport interface AhrefsPaginationOptions {\n limit?: number;\n offset?: number;\n}\n","// @power-seo/integrations — Shared HTTP Client\n// ----------------------------------------------------------------------------\n\nimport type { HttpClientConfig, HttpClient, AuthStrategy } from './types.js';\nimport { IntegrationApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_RATE_LIMIT = 600;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Record<string, string>,\n auth: AuthStrategy,\n): string {\n const url = new globalThis.URL(`${baseUrl}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n if (auth.type === 'query') {\n url.searchParams.set(auth.paramName, auth.value);\n }\n return url.toString();\n}\n\nfunction buildHeaders(auth: AuthStrategy): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (auth.type === 'bearer') {\n headers['Authorization'] = `Bearer ${auth.token}`;\n }\n return headers;\n}\n\nfunction getProviderFromUrl(baseUrl: string): string {\n if (baseUrl.includes('semrush')) return 'semrush';\n if (baseUrl.includes('ahrefs')) return 'ahrefs';\n return 'unknown';\n}\n\nexport function createHttpClient(config: HttpClientConfig): HttpClient {\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n const provider = getProviderFromUrl(config.baseUrl);\n\n async function makeRequest<T>(url: string, init: globalThis.RequestInit): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new globalThis.AbortController();\n const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await globalThis.fetch(url, {\n ...init,\n signal: controller.signal,\n });\n\n globalThis.clearTimeout(timeout);\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new IntegrationApiError(\n `${provider} API error: ${response.status} ${text}`,\n response.status,\n provider,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n globalThis.clearTimeout(timeout);\n if (err instanceof IntegrationApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new IntegrationApiError('Request failed', 500, provider);\n }\n\n return {\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = buildUrl(config.baseUrl, path, params ?? {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, { method: 'GET', headers });\n },\n\n async post<T>(path: string, body?: unknown): Promise<T> {\n const url = buildUrl(config.baseUrl, path, {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, {\n method: 'POST',\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n },\n };\n}\n","// @power-seo/integrations — Semrush Endpoints\n// ----------------------------------------------------------------------------\n\nexport const SEMRUSH_BASE_URL = 'https://api.semrush.com';\n\nexport function domainOverviewPath(): string {\n return '/analytics/v1/';\n}\n\nexport function organicKeywordsPath(): string {\n return '/analytics/v1/';\n}\n\nexport function backlinksPath(): string {\n return '/analytics/v1/';\n}\n\nexport function keywordDifficultyPath(): string {\n return '/analytics/v1/';\n}\n\nexport function relatedKeywordsPath(): string {\n return '/analytics/v1/';\n}\n\nexport function buildDomainOverviewParams(\n domain: string,\n database: string,\n): Record<string, string> {\n return {\n type: 'domain_ranks',\n domain,\n database,\n export_columns: 'Or,Ot,Oc,Ad,At,Ac,Bk,As',\n };\n}\n\nexport function buildOrganicKeywordsParams(\n domain: string,\n database: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n type: 'domain_organic',\n domain,\n database,\n display_limit: String(limit),\n display_offset: String(offset),\n export_columns: 'Ph,Po,Pp,Nq,Cp,Ur,Tr,Tc,Co,Td',\n };\n}\n\nexport function buildBacklinksParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n type: 'backlinks',\n target: domain,\n target_type: 'root_domain',\n display_limit: String(limit),\n display_offset: String(offset),\n export_columns: 'source_url,target_url,anchor,nofollow,first_seen,last_seen,source_ascore',\n };\n}\n\nexport function buildKeywordDifficultyParams(\n keywords: string[],\n database: string,\n): Record<string, string> {\n return {\n type: 'phrase_all',\n phrase: keywords.join(';'),\n database,\n export_columns: 'Ph,Kd,Nq,Cp,Co,Nr',\n };\n}\n\nexport function buildRelatedKeywordsParams(\n keyword: string,\n database: string,\n): Record<string, string> {\n return {\n type: 'phrase_related',\n phrase: keyword,\n database,\n export_columns: 'Ph,Nq,Cp,Co,Nr,Rr',\n };\n}\n","// @power-seo/integrations — Semrush Client\n// ----------------------------------------------------------------------------\n\nimport type {\n SemrushClient,\n SemrushDomainOverview,\n SemrushKeywordData,\n SemrushBacklinkData,\n SemrushKeywordDifficulty,\n SemrushRelatedKeyword,\n PaginatedResponse,\n SemrushPaginationOptions,\n HttpClientConfig,\n} from '../types.js';\nimport { createHttpClient } from '../http.js';\nimport {\n SEMRUSH_BASE_URL,\n domainOverviewPath,\n organicKeywordsPath,\n backlinksPath,\n keywordDifficultyPath,\n relatedKeywordsPath,\n buildDomainOverviewParams,\n buildOrganicKeywordsParams,\n buildBacklinksParams,\n buildKeywordDifficultyParams,\n buildRelatedKeywordsParams,\n} from './endpoints.js';\nimport type {\n SemrushRawDomainResponse,\n SemrushApiResponse,\n SemrushRawKeywordResponse,\n SemrushRawBacklinkResponse,\n SemrushRawKeywordDifficultyResponse,\n SemrushRawRelatedKeywordResponse,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 100;\nconst DEFAULT_DATABASE = 'us';\n\nexport function createSemrushClient(\n apiKey: string,\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\n): SemrushClient {\n const http = createHttpClient({\n baseUrl: SEMRUSH_BASE_URL,\n auth: { type: 'query', paramName: 'key', value: apiKey },\n ...config,\n });\n\n return {\n async getDomainOverview(domain, database = DEFAULT_DATABASE) {\n const params = buildDomainOverviewParams(domain, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawDomainResponse>>(\n domainOverviewPath(),\n params,\n );\n const entry = raw.data[0];\n return {\n domain,\n organicKeywords: entry?.Or ?? 0,\n organicTraffic: entry?.Ot ?? 0,\n organicCost: entry?.Oc ?? 0,\n paidKeywords: entry?.Ad ?? 0,\n paidTraffic: entry?.At ?? 0,\n paidCost: entry?.Ac ?? 0,\n backlinks: entry?.Bk ?? 0,\n authorityScore: entry?.As ?? 0,\n } satisfies SemrushDomainOverview;\n },\n\n async getOrganicKeywords(domain, options?: SemrushPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const database = options?.database ?? DEFAULT_DATABASE;\n const params = buildOrganicKeywordsParams(domain, database, limit, offset);\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordResponse>>(\n organicKeywordsPath(),\n params,\n );\n const data: SemrushKeywordData[] = (raw.data ?? []).map((r) => ({\n keyword: r.Ph,\n position: r.Po,\n previousPosition: r.Pp,\n searchVolume: r.Nq,\n cpc: r.Cp,\n url: r.Ur,\n traffic: r.Tr,\n trafficPercent: r.Td,\n trafficCost: r.Tc,\n competition: r.Co,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<SemrushKeywordData>;\n },\n\n async getBacklinks(domain, options?: SemrushPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildBacklinksParams(domain, limit, offset);\n const raw = await http.get<SemrushApiResponse<SemrushRawBacklinkResponse>>(\n backlinksPath(),\n params,\n );\n const data: SemrushBacklinkData[] = (raw.data ?? []).map((r) => ({\n sourceUrl: r.source_url,\n targetUrl: r.target_url,\n anchorText: r.anchor,\n isNoFollow: r.nofollow,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n sourceAuthorityScore: r.source_ascore,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<SemrushBacklinkData>;\n },\n\n async getKeywordDifficulty(keywords, database = DEFAULT_DATABASE) {\n const params = buildKeywordDifficultyParams(keywords, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordDifficultyResponse>>(\n keywordDifficultyPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): SemrushKeywordDifficulty => ({\n keyword: r.Ph,\n difficulty: r.Kd,\n searchVolume: r.Nq,\n cpc: r.Cp,\n competition: r.Co,\n results: r.Nr,\n }),\n );\n },\n\n async getRelatedKeywords(keyword, database = DEFAULT_DATABASE) {\n const params = buildRelatedKeywordsParams(keyword, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawRelatedKeywordResponse>>(\n relatedKeywordsPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): SemrushRelatedKeyword => ({\n keyword: r.Ph,\n searchVolume: r.Nq,\n cpc: r.Cp,\n competition: r.Co,\n results: r.Nr,\n relatedTo: r.Rr,\n }),\n );\n },\n };\n}\n","// @power-seo/integrations — Ahrefs Endpoints\n// ----------------------------------------------------------------------------\n\nexport const AHREFS_BASE_URL = 'https://api.ahrefs.com/v3';\n\nexport function siteOverviewPath(): string {\n return '/site-explorer/overview';\n}\n\nexport function organicKeywordsPath(): string {\n return '/site-explorer/organic-keywords';\n}\n\nexport function backlinksPath(): string {\n return '/site-explorer/backlinks';\n}\n\nexport function keywordDifficultyPath(): string {\n return '/keywords-explorer/keyword-difficulty';\n}\n\nexport function referringDomainsPath(): string {\n return '/site-explorer/referring-domains';\n}\n\nexport function buildSiteOverviewParams(domain: string): Record<string, string> {\n return { target: domain, mode: 'domain' };\n}\n\nexport function buildOrganicKeywordsParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n\nexport function buildBacklinksParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n\nexport function buildKeywordDifficultyParams(keywords: string[]): Record<string, string> {\n return {\n keywords: keywords.join(','),\n };\n}\n\nexport function buildReferringDomainsParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n","// @power-seo/integrations — Ahrefs Client\n// ----------------------------------------------------------------------------\n\nimport type {\n AhrefsClient,\n AhrefsSiteOverview,\n AhrefsOrganicKeyword,\n AhrefsBacklink,\n AhrefsKeywordDifficulty,\n AhrefsReferringDomain,\n PaginatedResponse,\n AhrefsPaginationOptions,\n HttpClientConfig,\n} from '../types.js';\nimport { createHttpClient } from '../http.js';\nimport {\n AHREFS_BASE_URL,\n siteOverviewPath,\n organicKeywordsPath,\n backlinksPath,\n keywordDifficultyPath,\n referringDomainsPath,\n buildSiteOverviewParams,\n buildOrganicKeywordsParams,\n buildBacklinksParams,\n buildKeywordDifficultyParams,\n buildReferringDomainsParams,\n} from './endpoints.js';\nimport type {\n AhrefsRawSiteOverview,\n AhrefsApiResponse,\n AhrefsRawOrganicKeyword,\n AhrefsRawBacklink,\n AhrefsRawKeywordDifficulty,\n AhrefsRawReferringDomain,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 100;\n\nexport function createAhrefsClient(\n apiToken: string,\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\n): AhrefsClient {\n const http = createHttpClient({\n baseUrl: AHREFS_BASE_URL,\n auth: { type: 'bearer', token: apiToken },\n ...config,\n });\n\n return {\n async getSiteOverview(domain) {\n const params = buildSiteOverviewParams(domain);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawSiteOverview>>(\n siteOverviewPath(),\n params,\n );\n const entry = raw.data[0];\n return {\n domain,\n domainRating: entry?.domain_rating ?? 0,\n urlRating: entry?.url_rating ?? 0,\n backlinks: entry?.backlinks ?? 0,\n referringDomains: entry?.referring_domains ?? 0,\n organicKeywords: entry?.organic_keywords ?? 0,\n organicTraffic: entry?.organic_traffic ?? 0,\n trafficValue: entry?.traffic_value ?? 0,\n } satisfies AhrefsSiteOverview;\n },\n\n async getOrganicKeywords(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildOrganicKeywordsParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawOrganicKeyword>>(\n organicKeywordsPath(),\n params,\n );\n const data: AhrefsOrganicKeyword[] = (raw.data ?? []).map((r) => ({\n keyword: r.keyword,\n position: r.position,\n searchVolume: r.volume,\n cpc: r.cpc,\n url: r.url,\n traffic: r.traffic,\n trafficPercent: r.traffic_percent,\n difficulty: r.difficulty,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsOrganicKeyword>;\n },\n\n async getBacklinks(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildBacklinksParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawBacklink>>(backlinksPath(), params);\n const data: AhrefsBacklink[] = (raw.data ?? []).map((r) => ({\n sourceUrl: r.url_from,\n targetUrl: r.url_to,\n anchorText: r.anchor,\n domainRating: r.domain_rating,\n urlRating: r.url_rating,\n isDoFollow: r.is_dofollow,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsBacklink>;\n },\n\n async getKeywordDifficulty(keywords) {\n const params = buildKeywordDifficultyParams(keywords);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawKeywordDifficulty>>(\n keywordDifficultyPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): AhrefsKeywordDifficulty => ({\n keyword: r.keyword,\n difficulty: r.difficulty,\n searchVolume: r.volume,\n cpc: r.cpc,\n clicks: r.clicks,\n globalVolume: r.global_volume,\n }),\n );\n },\n\n async getReferringDomains(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildReferringDomainsParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawReferringDomain>>(\n referringDomainsPath(),\n params,\n );\n const data: AhrefsReferringDomain[] = (raw.data ?? []).map((r) => ({\n domain: r.domain,\n domainRating: r.domain_rating,\n backlinks: r.backlinks,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n isDoFollow: r.is_dofollow,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsReferringDomain>;\n },\n };\n}\n"],"mappings":"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","names":["organicKeywordsPath","backlinksPath","keywordDifficultyPath","buildOrganicKeywordsParams","buildBacklinksParams","buildKeywordDifficultyParams","DEFAULT_LIMIT","buildOrganicKeywordsParams","organicKeywordsPath","buildBacklinksParams","backlinksPath","buildKeywordDifficultyParams","keywordDifficultyPath"]}
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/http.ts","../src/semrush/endpoints.ts","../src/semrush/client.ts","../src/ahrefs/endpoints.ts","../src/ahrefs/client.ts"],"sourcesContent":["// @power-seo/integrations — Main Entry Point\n// ----------------------------------------------------------------------------\n\r\n// Types\r\nexport type {\r\n AuthStrategy,\r\n HttpClientConfig,\r\n HttpClient,\r\n PaginatedResponse,\r\n SemrushDomainOverview,\r\n SemrushKeywordData,\r\n SemrushBacklinkData,\r\n SemrushKeywordDifficulty,\r\n SemrushRelatedKeyword,\r\n SemrushClient,\r\n SemrushPaginationOptions,\r\n AhrefsSiteOverview,\r\n AhrefsOrganicKeyword,\r\n AhrefsBacklink,\r\n AhrefsKeywordDifficulty,\r\n AhrefsReferringDomain,\r\n AhrefsClient,\r\n AhrefsPaginationOptions,\r\n} from './types.js';\r\n\r\n// Error class\r\nexport { IntegrationApiError } from './types.js';\r\n\r\n// HTTP Client\r\nexport { createHttpClient } from './http.js';\r\n\r\n// Semrush\r\nexport { createSemrushClient } from './semrush/client.js';\r\n\r\n// Ahrefs\r\nexport { createAhrefsClient } from './ahrefs/client.js';\r\n","// @power-seo/integrations — Types\n// ----------------------------------------------------------------------------\n\r\n// --- HTTP Client Types ---\r\n\r\nexport type AuthStrategy =\r\n | { type: 'query'; paramName: string; value: string }\r\n | { type: 'bearer'; token: string };\r\n\r\nexport interface HttpClientConfig {\r\n baseUrl: string;\r\n auth: AuthStrategy;\r\n rateLimitPerMinute?: number;\r\n maxRetries?: number;\r\n timeoutMs?: number;\r\n}\r\n\r\nexport interface HttpClient {\r\n get: <T>(path: string, params?: Record<string, string>) => Promise<T>;\r\n post: <T>(path: string, body?: unknown) => Promise<T>;\r\n}\r\n\r\n// --- Error ---\r\n\r\nexport class IntegrationApiError extends Error {\r\n readonly status: number;\r\n readonly provider: string;\r\n readonly retryable: boolean;\r\n\r\n constructor(message: string, status: number, provider: string) {\r\n super(message);\r\n this.name = 'IntegrationApiError';\r\n this.status = status;\r\n this.provider = provider;\r\n this.retryable = status === 429 || status >= 500;\r\n }\r\n}\r\n\r\n// --- Pagination ---\r\n\r\nexport interface PaginatedResponse<T> {\r\n data: T[];\r\n total: number;\r\n offset: number;\r\n limit: number;\r\n hasMore: boolean;\r\n}\r\n\r\n// --- Semrush Types ---\r\n\r\nexport interface SemrushDomainOverview {\r\n domain: string;\r\n organicKeywords: number;\r\n organicTraffic: number;\r\n organicCost: number;\r\n paidKeywords: number;\r\n paidTraffic: number;\r\n paidCost: number;\r\n backlinks: number;\r\n authorityScore: number;\r\n}\r\n\r\nexport interface SemrushKeywordData {\r\n keyword: string;\r\n position: number;\r\n previousPosition: number;\r\n searchVolume: number;\r\n cpc: number;\r\n url: string;\r\n traffic: number;\r\n trafficPercent: number;\r\n trafficCost: number;\r\n competition: number;\r\n}\r\n\r\nexport interface SemrushBacklinkData {\r\n sourceUrl: string;\r\n targetUrl: string;\r\n anchorText: string;\r\n isNoFollow: boolean;\r\n firstSeen: string;\r\n lastSeen: string;\r\n sourceAuthorityScore: number;\r\n}\r\n\r\nexport interface SemrushKeywordDifficulty {\r\n keyword: string;\r\n difficulty: number;\r\n searchVolume: number;\r\n cpc: number;\r\n competition: number;\r\n results: number;\r\n}\r\n\r\nexport interface SemrushRelatedKeyword {\r\n keyword: string;\r\n searchVolume: number;\r\n cpc: number;\r\n competition: number;\r\n results: number;\r\n relatedTo: string;\r\n}\r\n\r\nexport interface SemrushClient {\r\n getDomainOverview: (domain: string, database?: string) => Promise<SemrushDomainOverview>;\r\n getOrganicKeywords: (\r\n domain: string,\r\n options?: SemrushPaginationOptions,\r\n ) => Promise<PaginatedResponse<SemrushKeywordData>>;\r\n getBacklinks: (\r\n domain: string,\r\n options?: SemrushPaginationOptions,\r\n ) => Promise<PaginatedResponse<SemrushBacklinkData>>;\r\n getKeywordDifficulty: (\r\n keywords: string[],\r\n database?: string,\r\n ) => Promise<SemrushKeywordDifficulty[]>;\r\n getRelatedKeywords: (keyword: string, database?: string) => Promise<SemrushRelatedKeyword[]>;\r\n}\r\n\r\nexport interface SemrushPaginationOptions {\r\n limit?: number;\r\n offset?: number;\r\n database?: string;\r\n}\r\n\r\n// --- Ahrefs Types ---\r\n\r\nexport interface AhrefsSiteOverview {\r\n domain: string;\r\n domainRating: number;\r\n urlRating: number;\r\n backlinks: number;\r\n referringDomains: number;\r\n organicKeywords: number;\r\n organicTraffic: number;\r\n trafficValue: number;\r\n}\r\n\r\nexport interface AhrefsOrganicKeyword {\r\n keyword: string;\r\n position: number;\r\n searchVolume: number;\r\n cpc: number;\r\n url: string;\r\n traffic: number;\r\n trafficPercent: number;\r\n difficulty: number;\r\n}\r\n\r\nexport interface AhrefsBacklink {\r\n sourceUrl: string;\r\n targetUrl: string;\r\n anchorText: string;\r\n domainRating: number;\r\n urlRating: number;\r\n isDoFollow: boolean;\r\n firstSeen: string;\r\n lastSeen: string;\r\n}\r\n\r\nexport interface AhrefsKeywordDifficulty {\r\n keyword: string;\r\n difficulty: number;\r\n searchVolume: number;\r\n cpc: number;\r\n clicks: number;\r\n globalVolume: number;\r\n}\r\n\r\nexport interface AhrefsReferringDomain {\r\n domain: string;\r\n domainRating: number;\r\n backlinks: number;\r\n firstSeen: string;\r\n lastSeen: string;\r\n isDoFollow: boolean;\r\n}\r\n\r\nexport interface AhrefsClient {\r\n getSiteOverview: (domain: string) => Promise<AhrefsSiteOverview>;\r\n getOrganicKeywords: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsOrganicKeyword>>;\r\n getBacklinks: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsBacklink>>;\r\n getKeywordDifficulty: (keywords: string[]) => Promise<AhrefsKeywordDifficulty[]>;\r\n getReferringDomains: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsReferringDomain>>;\r\n}\r\n\r\nexport interface AhrefsPaginationOptions {\r\n limit?: number;\r\n offset?: number;\r\n}\r\n","// @power-seo/integrations — Shared HTTP Client\n// ----------------------------------------------------------------------------\n\nimport type { HttpClientConfig, HttpClient, AuthStrategy } from './types.js';\nimport { IntegrationApiError } from './types.js';\nimport {\n createTokenBucket,\n consumeToken,\n getWaitTime,\n sleep,\n fetchJsonWithRetry,\n} from '@power-seo/core';\n\nconst DEFAULT_RATE_LIMIT = 600;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Record<string, string>,\n auth: AuthStrategy,\n): string {\n const url = new globalThis.URL(`${baseUrl}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n if (auth.type === 'query') {\n url.searchParams.set(auth.paramName, auth.value);\n }\n return url.toString();\n}\n\nfunction buildHeaders(auth: AuthStrategy): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (auth.type === 'bearer') {\n headers['Authorization'] = `Bearer ${auth.token}`;\n }\n return headers;\n}\n\nfunction getProviderFromUrl(baseUrl: string): string {\n if (baseUrl.includes('semrush')) return 'semrush';\n if (baseUrl.includes('ahrefs')) return 'ahrefs';\n return 'unknown';\n}\n\nexport function createHttpClient(config: HttpClientConfig): HttpClient {\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n const provider = getProviderFromUrl(config.baseUrl);\n\n async function makeRequest<T>(url: string, init: globalThis.RequestInit): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n return fetchJsonWithRetry<T, IntegrationApiError>({\n maxRetries,\n doFetch: async () => {\n const controller = new globalThis.AbortController();\n const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await globalThis.fetch(url, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n globalThis.clearTimeout(timeout);\n }\n },\n buildError: (status, snippet) =>\n new IntegrationApiError(`${provider} API error: ${status} ${snippet}`, status, provider),\n fallbackError: () => new IntegrationApiError('Request failed', 500, provider),\n });\n }\n\n return {\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = buildUrl(config.baseUrl, path, params ?? {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, { method: 'GET', headers });\n },\n\n async post<T>(path: string, body?: unknown): Promise<T> {\n const url = buildUrl(config.baseUrl, path, {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, {\n method: 'POST',\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n },\n };\n}\n","// @power-seo/integrations — Semrush Endpoints\n// ----------------------------------------------------------------------------\n\r\nexport const SEMRUSH_BASE_URL = 'https://api.semrush.com';\r\n\r\nexport function domainOverviewPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function organicKeywordsPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function backlinksPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function keywordDifficultyPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function relatedKeywordsPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function buildDomainOverviewParams(\r\n domain: string,\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'domain_ranks',\r\n domain,\r\n database,\r\n export_columns: 'Or,Ot,Oc,Ad,At,Ac,Bk,As',\r\n };\r\n}\r\n\r\nexport function buildOrganicKeywordsParams(\r\n domain: string,\r\n database: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n type: 'domain_organic',\r\n domain,\r\n database,\r\n display_limit: String(limit),\r\n display_offset: String(offset),\r\n export_columns: 'Ph,Po,Pp,Nq,Cp,Ur,Tr,Tc,Co,Td',\r\n };\r\n}\r\n\r\nexport function buildBacklinksParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n type: 'backlinks',\r\n target: domain,\r\n target_type: 'root_domain',\r\n display_limit: String(limit),\r\n display_offset: String(offset),\r\n export_columns: 'source_url,target_url,anchor,nofollow,first_seen,last_seen,source_ascore',\r\n };\r\n}\r\n\r\nexport function buildKeywordDifficultyParams(\r\n keywords: string[],\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'phrase_all',\r\n phrase: keywords.join(';'),\r\n database,\r\n export_columns: 'Ph,Kd,Nq,Cp,Co,Nr',\r\n };\r\n}\r\n\r\nexport function buildRelatedKeywordsParams(\r\n keyword: string,\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'phrase_related',\r\n phrase: keyword,\r\n database,\r\n export_columns: 'Ph,Nq,Cp,Co,Nr,Rr',\r\n };\r\n}\r\n","// @power-seo/integrations — Semrush Client\n// ----------------------------------------------------------------------------\n\r\nimport type {\r\n SemrushClient,\r\n SemrushDomainOverview,\r\n SemrushKeywordData,\r\n SemrushBacklinkData,\r\n SemrushKeywordDifficulty,\r\n SemrushRelatedKeyword,\r\n PaginatedResponse,\r\n SemrushPaginationOptions,\r\n HttpClientConfig,\r\n} from '../types.js';\r\nimport { createHttpClient } from '../http.js';\r\nimport {\r\n SEMRUSH_BASE_URL,\r\n domainOverviewPath,\r\n organicKeywordsPath,\r\n backlinksPath,\r\n keywordDifficultyPath,\r\n relatedKeywordsPath,\r\n buildDomainOverviewParams,\r\n buildOrganicKeywordsParams,\r\n buildBacklinksParams,\r\n buildKeywordDifficultyParams,\r\n buildRelatedKeywordsParams,\r\n} from './endpoints.js';\r\nimport type {\r\n SemrushRawDomainResponse,\r\n SemrushApiResponse,\r\n SemrushRawKeywordResponse,\r\n SemrushRawBacklinkResponse,\r\n SemrushRawKeywordDifficultyResponse,\r\n SemrushRawRelatedKeywordResponse,\r\n} from './types.js';\r\n\r\nconst DEFAULT_LIMIT = 100;\r\nconst DEFAULT_DATABASE = 'us';\r\n\r\nexport function createSemrushClient(\r\n apiKey: string,\r\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\r\n): SemrushClient {\r\n const http = createHttpClient({\r\n baseUrl: SEMRUSH_BASE_URL,\r\n auth: { type: 'query', paramName: 'key', value: apiKey },\r\n ...config,\r\n });\r\n\r\n return {\r\n async getDomainOverview(domain, database = DEFAULT_DATABASE) {\r\n const params = buildDomainOverviewParams(domain, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawDomainResponse>>(\r\n domainOverviewPath(),\r\n params,\r\n );\r\n const entry = raw.data[0];\r\n return {\r\n domain,\r\n organicKeywords: entry?.Or ?? 0,\r\n organicTraffic: entry?.Ot ?? 0,\r\n organicCost: entry?.Oc ?? 0,\r\n paidKeywords: entry?.Ad ?? 0,\r\n paidTraffic: entry?.At ?? 0,\r\n paidCost: entry?.Ac ?? 0,\r\n backlinks: entry?.Bk ?? 0,\r\n authorityScore: entry?.As ?? 0,\r\n } satisfies SemrushDomainOverview;\r\n },\r\n\r\n async getOrganicKeywords(domain, options?: SemrushPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const database = options?.database ?? DEFAULT_DATABASE;\r\n const params = buildOrganicKeywordsParams(domain, database, limit, offset);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordResponse>>(\r\n organicKeywordsPath(),\r\n params,\r\n );\r\n const data: SemrushKeywordData[] = (raw.data ?? []).map((r) => ({\r\n keyword: r.Ph,\r\n position: r.Po,\r\n previousPosition: r.Pp,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n url: r.Ur,\r\n traffic: r.Tr,\r\n trafficPercent: r.Td,\r\n trafficCost: r.Tc,\r\n competition: r.Co,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<SemrushKeywordData>;\r\n },\r\n\r\n async getBacklinks(domain, options?: SemrushPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildBacklinksParams(domain, limit, offset);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawBacklinkResponse>>(\r\n backlinksPath(),\r\n params,\r\n );\r\n const data: SemrushBacklinkData[] = (raw.data ?? []).map((r) => ({\r\n sourceUrl: r.source_url,\r\n targetUrl: r.target_url,\r\n anchorText: r.anchor,\r\n isNoFollow: r.nofollow,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n sourceAuthorityScore: r.source_ascore,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<SemrushBacklinkData>;\r\n },\r\n\r\n async getKeywordDifficulty(keywords, database = DEFAULT_DATABASE) {\r\n const params = buildKeywordDifficultyParams(keywords, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordDifficultyResponse>>(\r\n keywordDifficultyPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): SemrushKeywordDifficulty => ({\r\n keyword: r.Ph,\r\n difficulty: r.Kd,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n competition: r.Co,\r\n results: r.Nr,\r\n }),\r\n );\r\n },\r\n\r\n async getRelatedKeywords(keyword, database = DEFAULT_DATABASE) {\r\n const params = buildRelatedKeywordsParams(keyword, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawRelatedKeywordResponse>>(\r\n relatedKeywordsPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): SemrushRelatedKeyword => ({\r\n keyword: r.Ph,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n competition: r.Co,\r\n results: r.Nr,\r\n relatedTo: r.Rr,\r\n }),\r\n );\r\n },\r\n };\r\n}\r\n","// @power-seo/integrations — Ahrefs Endpoints\n// ----------------------------------------------------------------------------\n\r\nexport const AHREFS_BASE_URL = 'https://api.ahrefs.com/v3';\r\n\r\nexport function siteOverviewPath(): string {\r\n return '/site-explorer/overview';\r\n}\r\n\r\nexport function organicKeywordsPath(): string {\r\n return '/site-explorer/organic-keywords';\r\n}\r\n\r\nexport function backlinksPath(): string {\r\n return '/site-explorer/backlinks';\r\n}\r\n\r\nexport function keywordDifficultyPath(): string {\r\n return '/keywords-explorer/keyword-difficulty';\r\n}\r\n\r\nexport function referringDomainsPath(): string {\r\n return '/site-explorer/referring-domains';\r\n}\r\n\r\nexport function buildSiteOverviewParams(domain: string): Record<string, string> {\r\n return { target: domain, mode: 'domain' };\r\n}\r\n\r\nexport function buildOrganicKeywordsParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n\r\nexport function buildBacklinksParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n\r\nexport function buildKeywordDifficultyParams(keywords: string[]): Record<string, string> {\r\n return {\r\n keywords: keywords.join(','),\r\n };\r\n}\r\n\r\nexport function buildReferringDomainsParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n","// @power-seo/integrations — Ahrefs Client\n// ----------------------------------------------------------------------------\n\r\nimport type {\r\n AhrefsClient,\r\n AhrefsSiteOverview,\r\n AhrefsOrganicKeyword,\r\n AhrefsBacklink,\r\n AhrefsKeywordDifficulty,\r\n AhrefsReferringDomain,\r\n PaginatedResponse,\r\n AhrefsPaginationOptions,\r\n HttpClientConfig,\r\n} from '../types.js';\r\nimport { createHttpClient } from '../http.js';\r\nimport {\r\n AHREFS_BASE_URL,\r\n siteOverviewPath,\r\n organicKeywordsPath,\r\n backlinksPath,\r\n keywordDifficultyPath,\r\n referringDomainsPath,\r\n buildSiteOverviewParams,\r\n buildOrganicKeywordsParams,\r\n buildBacklinksParams,\r\n buildKeywordDifficultyParams,\r\n buildReferringDomainsParams,\r\n} from './endpoints.js';\r\nimport type {\r\n AhrefsRawSiteOverview,\r\n AhrefsApiResponse,\r\n AhrefsRawOrganicKeyword,\r\n AhrefsRawBacklink,\r\n AhrefsRawKeywordDifficulty,\r\n AhrefsRawReferringDomain,\r\n} from './types.js';\r\n\r\nconst DEFAULT_LIMIT = 100;\r\n\r\nexport function createAhrefsClient(\r\n apiToken: string,\r\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\r\n): AhrefsClient {\r\n const http = createHttpClient({\r\n baseUrl: AHREFS_BASE_URL,\r\n auth: { type: 'bearer', token: apiToken },\r\n ...config,\r\n });\r\n\r\n return {\r\n async getSiteOverview(domain) {\r\n const params = buildSiteOverviewParams(domain);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawSiteOverview>>(\r\n siteOverviewPath(),\r\n params,\r\n );\r\n const entry = raw.data[0];\r\n return {\r\n domain,\r\n domainRating: entry?.domain_rating ?? 0,\r\n urlRating: entry?.url_rating ?? 0,\r\n backlinks: entry?.backlinks ?? 0,\r\n referringDomains: entry?.referring_domains ?? 0,\r\n organicKeywords: entry?.organic_keywords ?? 0,\r\n organicTraffic: entry?.organic_traffic ?? 0,\r\n trafficValue: entry?.traffic_value ?? 0,\r\n } satisfies AhrefsSiteOverview;\r\n },\r\n\r\n async getOrganicKeywords(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildOrganicKeywordsParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawOrganicKeyword>>(\r\n organicKeywordsPath(),\r\n params,\r\n );\r\n const data: AhrefsOrganicKeyword[] = (raw.data ?? []).map((r) => ({\r\n keyword: r.keyword,\r\n position: r.position,\r\n searchVolume: r.volume,\r\n cpc: r.cpc,\r\n url: r.url,\r\n traffic: r.traffic,\r\n trafficPercent: r.traffic_percent,\r\n difficulty: r.difficulty,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsOrganicKeyword>;\r\n },\r\n\r\n async getBacklinks(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildBacklinksParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawBacklink>>(backlinksPath(), params);\r\n const data: AhrefsBacklink[] = (raw.data ?? []).map((r) => ({\r\n sourceUrl: r.url_from,\r\n targetUrl: r.url_to,\r\n anchorText: r.anchor,\r\n domainRating: r.domain_rating,\r\n urlRating: r.url_rating,\r\n isDoFollow: r.is_dofollow,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsBacklink>;\r\n },\r\n\r\n async getKeywordDifficulty(keywords) {\r\n const params = buildKeywordDifficultyParams(keywords);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawKeywordDifficulty>>(\r\n keywordDifficultyPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): AhrefsKeywordDifficulty => ({\r\n keyword: r.keyword,\r\n difficulty: r.difficulty,\r\n searchVolume: r.volume,\r\n cpc: r.cpc,\r\n clicks: r.clicks,\r\n globalVolume: r.global_volume,\r\n }),\r\n );\r\n },\r\n\r\n async getReferringDomains(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildReferringDomainsParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawReferringDomain>>(\r\n referringDomainsPath(),\r\n params,\r\n );\r\n const data: AhrefsReferringDomain[] = (raw.data ?? []).map((r) => ({\r\n domain: r.domain,\r\n domainRating: r.domain_rating,\r\n backlinks: r.backlinks,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n isDoFollow: r.is_dofollow,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsReferringDomain>;\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":["organicKeywordsPath","backlinksPath","keywordDifficultyPath","buildOrganicKeywordsParams","buildBacklinksParams","buildKeywordDifficultyParams","DEFAULT_LIMIT","buildOrganicKeywordsParams","organicKeywordsPath","buildBacklinksParams","backlinksPath","buildKeywordDifficultyParams","keywordDifficultyPath"]}

@@ -16,3 +16,9 @@ // src/types.ts

// src/http.ts
import { createTokenBucket, consumeToken, getWaitTime, sleep } from "@power-seo/core";
import {
createTokenBucket,
consumeToken,
getWaitTime,
sleep,
fetchJsonWithRetry
} from "@power-seo/core";
var DEFAULT_RATE_LIMIT = 600;

@@ -57,42 +63,19 @@ var DEFAULT_MAX_RETRIES = 3;

consumeToken(bucket);
let lastError = null;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const controller = new globalThis.AbortController();
const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await globalThis.fetch(url, {
...init,
signal: controller.signal
});
globalThis.clearTimeout(timeout);
if (response.ok) {
return await response.json();
return fetchJsonWithRetry({
maxRetries,
doFetch: async () => {
const controller = new globalThis.AbortController();
const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);
try {
return await globalThis.fetch(url, {
...init,
signal: controller.signal
});
} finally {
globalThis.clearTimeout(timeout);
}
const text = await response.text();
const error = new IntegrationApiError(
`${provider} API error: ${response.status} ${text}`,
response.status,
provider
);
if (!error.retryable || attempt === maxRetries) {
throw error;
}
lastError = error;
const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
await sleep(backoff);
} catch (err) {
globalThis.clearTimeout(timeout);
if (err instanceof IntegrationApiError) {
if (!err.retryable || attempt === maxRetries) {
throw err;
}
lastError = err;
const backoff = Math.min(1e3 * Math.pow(2, attempt), 3e4);
await sleep(backoff);
} else {
throw err;
}
}
}
throw lastError ?? new IntegrationApiError("Request failed", 500, provider);
},
buildError: (status, snippet) => new IntegrationApiError(`${provider} API error: ${status} ${snippet}`, status, provider),
fallbackError: () => new IntegrationApiError("Request failed", 500, provider)
});
}

@@ -99,0 +82,0 @@ return {

@@ -1,1 +0,1 @@

{"version":3,"sources":["../src/types.ts","../src/http.ts","../src/semrush/endpoints.ts","../src/semrush/client.ts","../src/ahrefs/endpoints.ts","../src/ahrefs/client.ts"],"sourcesContent":["// @power-seo/integrations — Types\n// ----------------------------------------------------------------------------\n\n// --- HTTP Client Types ---\n\nexport type AuthStrategy =\n | { type: 'query'; paramName: string; value: string }\n | { type: 'bearer'; token: string };\n\nexport interface HttpClientConfig {\n baseUrl: string;\n auth: AuthStrategy;\n rateLimitPerMinute?: number;\n maxRetries?: number;\n timeoutMs?: number;\n}\n\nexport interface HttpClient {\n get: <T>(path: string, params?: Record<string, string>) => Promise<T>;\n post: <T>(path: string, body?: unknown) => Promise<T>;\n}\n\n// --- Error ---\n\nexport class IntegrationApiError extends Error {\n readonly status: number;\n readonly provider: string;\n readonly retryable: boolean;\n\n constructor(message: string, status: number, provider: string) {\n super(message);\n this.name = 'IntegrationApiError';\n this.status = status;\n this.provider = provider;\n this.retryable = status === 429 || status >= 500;\n }\n}\n\n// --- Pagination ---\n\nexport interface PaginatedResponse<T> {\n data: T[];\n total: number;\n offset: number;\n limit: number;\n hasMore: boolean;\n}\n\n// --- Semrush Types ---\n\nexport interface SemrushDomainOverview {\n domain: string;\n organicKeywords: number;\n organicTraffic: number;\n organicCost: number;\n paidKeywords: number;\n paidTraffic: number;\n paidCost: number;\n backlinks: number;\n authorityScore: number;\n}\n\nexport interface SemrushKeywordData {\n keyword: string;\n position: number;\n previousPosition: number;\n searchVolume: number;\n cpc: number;\n url: string;\n traffic: number;\n trafficPercent: number;\n trafficCost: number;\n competition: number;\n}\n\nexport interface SemrushBacklinkData {\n sourceUrl: string;\n targetUrl: string;\n anchorText: string;\n isNoFollow: boolean;\n firstSeen: string;\n lastSeen: string;\n sourceAuthorityScore: number;\n}\n\nexport interface SemrushKeywordDifficulty {\n keyword: string;\n difficulty: number;\n searchVolume: number;\n cpc: number;\n competition: number;\n results: number;\n}\n\nexport interface SemrushRelatedKeyword {\n keyword: string;\n searchVolume: number;\n cpc: number;\n competition: number;\n results: number;\n relatedTo: string;\n}\n\nexport interface SemrushClient {\n getDomainOverview: (domain: string, database?: string) => Promise<SemrushDomainOverview>;\n getOrganicKeywords: (\n domain: string,\n options?: SemrushPaginationOptions,\n ) => Promise<PaginatedResponse<SemrushKeywordData>>;\n getBacklinks: (\n domain: string,\n options?: SemrushPaginationOptions,\n ) => Promise<PaginatedResponse<SemrushBacklinkData>>;\n getKeywordDifficulty: (\n keywords: string[],\n database?: string,\n ) => Promise<SemrushKeywordDifficulty[]>;\n getRelatedKeywords: (keyword: string, database?: string) => Promise<SemrushRelatedKeyword[]>;\n}\n\nexport interface SemrushPaginationOptions {\n limit?: number;\n offset?: number;\n database?: string;\n}\n\n// --- Ahrefs Types ---\n\nexport interface AhrefsSiteOverview {\n domain: string;\n domainRating: number;\n urlRating: number;\n backlinks: number;\n referringDomains: number;\n organicKeywords: number;\n organicTraffic: number;\n trafficValue: number;\n}\n\nexport interface AhrefsOrganicKeyword {\n keyword: string;\n position: number;\n searchVolume: number;\n cpc: number;\n url: string;\n traffic: number;\n trafficPercent: number;\n difficulty: number;\n}\n\nexport interface AhrefsBacklink {\n sourceUrl: string;\n targetUrl: string;\n anchorText: string;\n domainRating: number;\n urlRating: number;\n isDoFollow: boolean;\n firstSeen: string;\n lastSeen: string;\n}\n\nexport interface AhrefsKeywordDifficulty {\n keyword: string;\n difficulty: number;\n searchVolume: number;\n cpc: number;\n clicks: number;\n globalVolume: number;\n}\n\nexport interface AhrefsReferringDomain {\n domain: string;\n domainRating: number;\n backlinks: number;\n firstSeen: string;\n lastSeen: string;\n isDoFollow: boolean;\n}\n\nexport interface AhrefsClient {\n getSiteOverview: (domain: string) => Promise<AhrefsSiteOverview>;\n getOrganicKeywords: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsOrganicKeyword>>;\n getBacklinks: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsBacklink>>;\n getKeywordDifficulty: (keywords: string[]) => Promise<AhrefsKeywordDifficulty[]>;\n getReferringDomains: (\n domain: string,\n options?: AhrefsPaginationOptions,\n ) => Promise<PaginatedResponse<AhrefsReferringDomain>>;\n}\n\nexport interface AhrefsPaginationOptions {\n limit?: number;\n offset?: number;\n}\n","// @power-seo/integrations — Shared HTTP Client\n// ----------------------------------------------------------------------------\n\nimport type { HttpClientConfig, HttpClient, AuthStrategy } from './types.js';\nimport { IntegrationApiError } from './types.js';\nimport { createTokenBucket, consumeToken, getWaitTime, sleep } from '@power-seo/core';\n\nconst DEFAULT_RATE_LIMIT = 600;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Record<string, string>,\n auth: AuthStrategy,\n): string {\n const url = new globalThis.URL(`${baseUrl}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n if (auth.type === 'query') {\n url.searchParams.set(auth.paramName, auth.value);\n }\n return url.toString();\n}\n\nfunction buildHeaders(auth: AuthStrategy): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (auth.type === 'bearer') {\n headers['Authorization'] = `Bearer ${auth.token}`;\n }\n return headers;\n}\n\nfunction getProviderFromUrl(baseUrl: string): string {\n if (baseUrl.includes('semrush')) return 'semrush';\n if (baseUrl.includes('ahrefs')) return 'ahrefs';\n return 'unknown';\n}\n\nexport function createHttpClient(config: HttpClientConfig): HttpClient {\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n const provider = getProviderFromUrl(config.baseUrl);\n\n async function makeRequest<T>(url: string, init: globalThis.RequestInit): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new globalThis.AbortController();\n const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await globalThis.fetch(url, {\n ...init,\n signal: controller.signal,\n });\n\n globalThis.clearTimeout(timeout);\n\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n const text = await response.text();\n const error = new IntegrationApiError(\n `${provider} API error: ${response.status} ${text}`,\n response.status,\n provider,\n );\n\n if (!error.retryable || attempt === maxRetries) {\n throw error;\n }\n\n lastError = error;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } catch (err) {\n globalThis.clearTimeout(timeout);\n if (err instanceof IntegrationApiError) {\n if (!err.retryable || attempt === maxRetries) {\n throw err;\n }\n lastError = err;\n const backoff = Math.min(1000 * Math.pow(2, attempt), 30_000);\n await sleep(backoff);\n } else {\n throw err;\n }\n }\n }\n\n throw lastError ?? new IntegrationApiError('Request failed', 500, provider);\n }\n\n return {\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = buildUrl(config.baseUrl, path, params ?? {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, { method: 'GET', headers });\n },\n\n async post<T>(path: string, body?: unknown): Promise<T> {\n const url = buildUrl(config.baseUrl, path, {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, {\n method: 'POST',\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n },\n };\n}\n","// @power-seo/integrations — Semrush Endpoints\n// ----------------------------------------------------------------------------\n\nexport const SEMRUSH_BASE_URL = 'https://api.semrush.com';\n\nexport function domainOverviewPath(): string {\n return '/analytics/v1/';\n}\n\nexport function organicKeywordsPath(): string {\n return '/analytics/v1/';\n}\n\nexport function backlinksPath(): string {\n return '/analytics/v1/';\n}\n\nexport function keywordDifficultyPath(): string {\n return '/analytics/v1/';\n}\n\nexport function relatedKeywordsPath(): string {\n return '/analytics/v1/';\n}\n\nexport function buildDomainOverviewParams(\n domain: string,\n database: string,\n): Record<string, string> {\n return {\n type: 'domain_ranks',\n domain,\n database,\n export_columns: 'Or,Ot,Oc,Ad,At,Ac,Bk,As',\n };\n}\n\nexport function buildOrganicKeywordsParams(\n domain: string,\n database: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n type: 'domain_organic',\n domain,\n database,\n display_limit: String(limit),\n display_offset: String(offset),\n export_columns: 'Ph,Po,Pp,Nq,Cp,Ur,Tr,Tc,Co,Td',\n };\n}\n\nexport function buildBacklinksParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n type: 'backlinks',\n target: domain,\n target_type: 'root_domain',\n display_limit: String(limit),\n display_offset: String(offset),\n export_columns: 'source_url,target_url,anchor,nofollow,first_seen,last_seen,source_ascore',\n };\n}\n\nexport function buildKeywordDifficultyParams(\n keywords: string[],\n database: string,\n): Record<string, string> {\n return {\n type: 'phrase_all',\n phrase: keywords.join(';'),\n database,\n export_columns: 'Ph,Kd,Nq,Cp,Co,Nr',\n };\n}\n\nexport function buildRelatedKeywordsParams(\n keyword: string,\n database: string,\n): Record<string, string> {\n return {\n type: 'phrase_related',\n phrase: keyword,\n database,\n export_columns: 'Ph,Nq,Cp,Co,Nr,Rr',\n };\n}\n","// @power-seo/integrations — Semrush Client\n// ----------------------------------------------------------------------------\n\nimport type {\n SemrushClient,\n SemrushDomainOverview,\n SemrushKeywordData,\n SemrushBacklinkData,\n SemrushKeywordDifficulty,\n SemrushRelatedKeyword,\n PaginatedResponse,\n SemrushPaginationOptions,\n HttpClientConfig,\n} from '../types.js';\nimport { createHttpClient } from '../http.js';\nimport {\n SEMRUSH_BASE_URL,\n domainOverviewPath,\n organicKeywordsPath,\n backlinksPath,\n keywordDifficultyPath,\n relatedKeywordsPath,\n buildDomainOverviewParams,\n buildOrganicKeywordsParams,\n buildBacklinksParams,\n buildKeywordDifficultyParams,\n buildRelatedKeywordsParams,\n} from './endpoints.js';\nimport type {\n SemrushRawDomainResponse,\n SemrushApiResponse,\n SemrushRawKeywordResponse,\n SemrushRawBacklinkResponse,\n SemrushRawKeywordDifficultyResponse,\n SemrushRawRelatedKeywordResponse,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 100;\nconst DEFAULT_DATABASE = 'us';\n\nexport function createSemrushClient(\n apiKey: string,\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\n): SemrushClient {\n const http = createHttpClient({\n baseUrl: SEMRUSH_BASE_URL,\n auth: { type: 'query', paramName: 'key', value: apiKey },\n ...config,\n });\n\n return {\n async getDomainOverview(domain, database = DEFAULT_DATABASE) {\n const params = buildDomainOverviewParams(domain, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawDomainResponse>>(\n domainOverviewPath(),\n params,\n );\n const entry = raw.data[0];\n return {\n domain,\n organicKeywords: entry?.Or ?? 0,\n organicTraffic: entry?.Ot ?? 0,\n organicCost: entry?.Oc ?? 0,\n paidKeywords: entry?.Ad ?? 0,\n paidTraffic: entry?.At ?? 0,\n paidCost: entry?.Ac ?? 0,\n backlinks: entry?.Bk ?? 0,\n authorityScore: entry?.As ?? 0,\n } satisfies SemrushDomainOverview;\n },\n\n async getOrganicKeywords(domain, options?: SemrushPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const database = options?.database ?? DEFAULT_DATABASE;\n const params = buildOrganicKeywordsParams(domain, database, limit, offset);\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordResponse>>(\n organicKeywordsPath(),\n params,\n );\n const data: SemrushKeywordData[] = (raw.data ?? []).map((r) => ({\n keyword: r.Ph,\n position: r.Po,\n previousPosition: r.Pp,\n searchVolume: r.Nq,\n cpc: r.Cp,\n url: r.Ur,\n traffic: r.Tr,\n trafficPercent: r.Td,\n trafficCost: r.Tc,\n competition: r.Co,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<SemrushKeywordData>;\n },\n\n async getBacklinks(domain, options?: SemrushPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildBacklinksParams(domain, limit, offset);\n const raw = await http.get<SemrushApiResponse<SemrushRawBacklinkResponse>>(\n backlinksPath(),\n params,\n );\n const data: SemrushBacklinkData[] = (raw.data ?? []).map((r) => ({\n sourceUrl: r.source_url,\n targetUrl: r.target_url,\n anchorText: r.anchor,\n isNoFollow: r.nofollow,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n sourceAuthorityScore: r.source_ascore,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<SemrushBacklinkData>;\n },\n\n async getKeywordDifficulty(keywords, database = DEFAULT_DATABASE) {\n const params = buildKeywordDifficultyParams(keywords, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordDifficultyResponse>>(\n keywordDifficultyPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): SemrushKeywordDifficulty => ({\n keyword: r.Ph,\n difficulty: r.Kd,\n searchVolume: r.Nq,\n cpc: r.Cp,\n competition: r.Co,\n results: r.Nr,\n }),\n );\n },\n\n async getRelatedKeywords(keyword, database = DEFAULT_DATABASE) {\n const params = buildRelatedKeywordsParams(keyword, database);\n const raw = await http.get<SemrushApiResponse<SemrushRawRelatedKeywordResponse>>(\n relatedKeywordsPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): SemrushRelatedKeyword => ({\n keyword: r.Ph,\n searchVolume: r.Nq,\n cpc: r.Cp,\n competition: r.Co,\n results: r.Nr,\n relatedTo: r.Rr,\n }),\n );\n },\n };\n}\n","// @power-seo/integrations — Ahrefs Endpoints\n// ----------------------------------------------------------------------------\n\nexport const AHREFS_BASE_URL = 'https://api.ahrefs.com/v3';\n\nexport function siteOverviewPath(): string {\n return '/site-explorer/overview';\n}\n\nexport function organicKeywordsPath(): string {\n return '/site-explorer/organic-keywords';\n}\n\nexport function backlinksPath(): string {\n return '/site-explorer/backlinks';\n}\n\nexport function keywordDifficultyPath(): string {\n return '/keywords-explorer/keyword-difficulty';\n}\n\nexport function referringDomainsPath(): string {\n return '/site-explorer/referring-domains';\n}\n\nexport function buildSiteOverviewParams(domain: string): Record<string, string> {\n return { target: domain, mode: 'domain' };\n}\n\nexport function buildOrganicKeywordsParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n\nexport function buildBacklinksParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n\nexport function buildKeywordDifficultyParams(keywords: string[]): Record<string, string> {\n return {\n keywords: keywords.join(','),\n };\n}\n\nexport function buildReferringDomainsParams(\n domain: string,\n limit: number,\n offset: number,\n): Record<string, string> {\n return {\n target: domain,\n mode: 'domain',\n limit: String(limit),\n offset: String(offset),\n };\n}\n","// @power-seo/integrations — Ahrefs Client\n// ----------------------------------------------------------------------------\n\nimport type {\n AhrefsClient,\n AhrefsSiteOverview,\n AhrefsOrganicKeyword,\n AhrefsBacklink,\n AhrefsKeywordDifficulty,\n AhrefsReferringDomain,\n PaginatedResponse,\n AhrefsPaginationOptions,\n HttpClientConfig,\n} from '../types.js';\nimport { createHttpClient } from '../http.js';\nimport {\n AHREFS_BASE_URL,\n siteOverviewPath,\n organicKeywordsPath,\n backlinksPath,\n keywordDifficultyPath,\n referringDomainsPath,\n buildSiteOverviewParams,\n buildOrganicKeywordsParams,\n buildBacklinksParams,\n buildKeywordDifficultyParams,\n buildReferringDomainsParams,\n} from './endpoints.js';\nimport type {\n AhrefsRawSiteOverview,\n AhrefsApiResponse,\n AhrefsRawOrganicKeyword,\n AhrefsRawBacklink,\n AhrefsRawKeywordDifficulty,\n AhrefsRawReferringDomain,\n} from './types.js';\n\nconst DEFAULT_LIMIT = 100;\n\nexport function createAhrefsClient(\n apiToken: string,\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\n): AhrefsClient {\n const http = createHttpClient({\n baseUrl: AHREFS_BASE_URL,\n auth: { type: 'bearer', token: apiToken },\n ...config,\n });\n\n return {\n async getSiteOverview(domain) {\n const params = buildSiteOverviewParams(domain);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawSiteOverview>>(\n siteOverviewPath(),\n params,\n );\n const entry = raw.data[0];\n return {\n domain,\n domainRating: entry?.domain_rating ?? 0,\n urlRating: entry?.url_rating ?? 0,\n backlinks: entry?.backlinks ?? 0,\n referringDomains: entry?.referring_domains ?? 0,\n organicKeywords: entry?.organic_keywords ?? 0,\n organicTraffic: entry?.organic_traffic ?? 0,\n trafficValue: entry?.traffic_value ?? 0,\n } satisfies AhrefsSiteOverview;\n },\n\n async getOrganicKeywords(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildOrganicKeywordsParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawOrganicKeyword>>(\n organicKeywordsPath(),\n params,\n );\n const data: AhrefsOrganicKeyword[] = (raw.data ?? []).map((r) => ({\n keyword: r.keyword,\n position: r.position,\n searchVolume: r.volume,\n cpc: r.cpc,\n url: r.url,\n traffic: r.traffic,\n trafficPercent: r.traffic_percent,\n difficulty: r.difficulty,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsOrganicKeyword>;\n },\n\n async getBacklinks(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildBacklinksParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawBacklink>>(backlinksPath(), params);\n const data: AhrefsBacklink[] = (raw.data ?? []).map((r) => ({\n sourceUrl: r.url_from,\n targetUrl: r.url_to,\n anchorText: r.anchor,\n domainRating: r.domain_rating,\n urlRating: r.url_rating,\n isDoFollow: r.is_dofollow,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsBacklink>;\n },\n\n async getKeywordDifficulty(keywords) {\n const params = buildKeywordDifficultyParams(keywords);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawKeywordDifficulty>>(\n keywordDifficultyPath(),\n params,\n );\n return (raw.data ?? []).map(\n (r): AhrefsKeywordDifficulty => ({\n keyword: r.keyword,\n difficulty: r.difficulty,\n searchVolume: r.volume,\n cpc: r.cpc,\n clicks: r.clicks,\n globalVolume: r.global_volume,\n }),\n );\n },\n\n async getReferringDomains(domain, options?: AhrefsPaginationOptions) {\n const limit = options?.limit ?? DEFAULT_LIMIT;\n const offset = options?.offset ?? 0;\n const params = buildReferringDomainsParams(domain, limit, offset);\n const raw = await http.get<AhrefsApiResponse<AhrefsRawReferringDomain>>(\n referringDomainsPath(),\n params,\n );\n const data: AhrefsReferringDomain[] = (raw.data ?? []).map((r) => ({\n domain: r.domain,\n domainRating: r.domain_rating,\n backlinks: r.backlinks,\n firstSeen: r.first_seen,\n lastSeen: r.last_seen,\n isDoFollow: r.is_dofollow,\n }));\n return {\n data,\n total: raw.total ?? 0,\n offset,\n limit,\n hasMore: data.length === limit,\n } satisfies PaginatedResponse<AhrefsReferringDomain>;\n },\n };\n}\n"],"mappings":"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","names":["organicKeywordsPath","backlinksPath","keywordDifficultyPath","buildOrganicKeywordsParams","buildBacklinksParams","buildKeywordDifficultyParams","DEFAULT_LIMIT","buildOrganicKeywordsParams","organicKeywordsPath","buildBacklinksParams","backlinksPath","buildKeywordDifficultyParams","keywordDifficultyPath"]}
{"version":3,"sources":["../src/types.ts","../src/http.ts","../src/semrush/endpoints.ts","../src/semrush/client.ts","../src/ahrefs/endpoints.ts","../src/ahrefs/client.ts"],"sourcesContent":["// @power-seo/integrations — Types\n// ----------------------------------------------------------------------------\n\r\n// --- HTTP Client Types ---\r\n\r\nexport type AuthStrategy =\r\n | { type: 'query'; paramName: string; value: string }\r\n | { type: 'bearer'; token: string };\r\n\r\nexport interface HttpClientConfig {\r\n baseUrl: string;\r\n auth: AuthStrategy;\r\n rateLimitPerMinute?: number;\r\n maxRetries?: number;\r\n timeoutMs?: number;\r\n}\r\n\r\nexport interface HttpClient {\r\n get: <T>(path: string, params?: Record<string, string>) => Promise<T>;\r\n post: <T>(path: string, body?: unknown) => Promise<T>;\r\n}\r\n\r\n// --- Error ---\r\n\r\nexport class IntegrationApiError extends Error {\r\n readonly status: number;\r\n readonly provider: string;\r\n readonly retryable: boolean;\r\n\r\n constructor(message: string, status: number, provider: string) {\r\n super(message);\r\n this.name = 'IntegrationApiError';\r\n this.status = status;\r\n this.provider = provider;\r\n this.retryable = status === 429 || status >= 500;\r\n }\r\n}\r\n\r\n// --- Pagination ---\r\n\r\nexport interface PaginatedResponse<T> {\r\n data: T[];\r\n total: number;\r\n offset: number;\r\n limit: number;\r\n hasMore: boolean;\r\n}\r\n\r\n// --- Semrush Types ---\r\n\r\nexport interface SemrushDomainOverview {\r\n domain: string;\r\n organicKeywords: number;\r\n organicTraffic: number;\r\n organicCost: number;\r\n paidKeywords: number;\r\n paidTraffic: number;\r\n paidCost: number;\r\n backlinks: number;\r\n authorityScore: number;\r\n}\r\n\r\nexport interface SemrushKeywordData {\r\n keyword: string;\r\n position: number;\r\n previousPosition: number;\r\n searchVolume: number;\r\n cpc: number;\r\n url: string;\r\n traffic: number;\r\n trafficPercent: number;\r\n trafficCost: number;\r\n competition: number;\r\n}\r\n\r\nexport interface SemrushBacklinkData {\r\n sourceUrl: string;\r\n targetUrl: string;\r\n anchorText: string;\r\n isNoFollow: boolean;\r\n firstSeen: string;\r\n lastSeen: string;\r\n sourceAuthorityScore: number;\r\n}\r\n\r\nexport interface SemrushKeywordDifficulty {\r\n keyword: string;\r\n difficulty: number;\r\n searchVolume: number;\r\n cpc: number;\r\n competition: number;\r\n results: number;\r\n}\r\n\r\nexport interface SemrushRelatedKeyword {\r\n keyword: string;\r\n searchVolume: number;\r\n cpc: number;\r\n competition: number;\r\n results: number;\r\n relatedTo: string;\r\n}\r\n\r\nexport interface SemrushClient {\r\n getDomainOverview: (domain: string, database?: string) => Promise<SemrushDomainOverview>;\r\n getOrganicKeywords: (\r\n domain: string,\r\n options?: SemrushPaginationOptions,\r\n ) => Promise<PaginatedResponse<SemrushKeywordData>>;\r\n getBacklinks: (\r\n domain: string,\r\n options?: SemrushPaginationOptions,\r\n ) => Promise<PaginatedResponse<SemrushBacklinkData>>;\r\n getKeywordDifficulty: (\r\n keywords: string[],\r\n database?: string,\r\n ) => Promise<SemrushKeywordDifficulty[]>;\r\n getRelatedKeywords: (keyword: string, database?: string) => Promise<SemrushRelatedKeyword[]>;\r\n}\r\n\r\nexport interface SemrushPaginationOptions {\r\n limit?: number;\r\n offset?: number;\r\n database?: string;\r\n}\r\n\r\n// --- Ahrefs Types ---\r\n\r\nexport interface AhrefsSiteOverview {\r\n domain: string;\r\n domainRating: number;\r\n urlRating: number;\r\n backlinks: number;\r\n referringDomains: number;\r\n organicKeywords: number;\r\n organicTraffic: number;\r\n trafficValue: number;\r\n}\r\n\r\nexport interface AhrefsOrganicKeyword {\r\n keyword: string;\r\n position: number;\r\n searchVolume: number;\r\n cpc: number;\r\n url: string;\r\n traffic: number;\r\n trafficPercent: number;\r\n difficulty: number;\r\n}\r\n\r\nexport interface AhrefsBacklink {\r\n sourceUrl: string;\r\n targetUrl: string;\r\n anchorText: string;\r\n domainRating: number;\r\n urlRating: number;\r\n isDoFollow: boolean;\r\n firstSeen: string;\r\n lastSeen: string;\r\n}\r\n\r\nexport interface AhrefsKeywordDifficulty {\r\n keyword: string;\r\n difficulty: number;\r\n searchVolume: number;\r\n cpc: number;\r\n clicks: number;\r\n globalVolume: number;\r\n}\r\n\r\nexport interface AhrefsReferringDomain {\r\n domain: string;\r\n domainRating: number;\r\n backlinks: number;\r\n firstSeen: string;\r\n lastSeen: string;\r\n isDoFollow: boolean;\r\n}\r\n\r\nexport interface AhrefsClient {\r\n getSiteOverview: (domain: string) => Promise<AhrefsSiteOverview>;\r\n getOrganicKeywords: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsOrganicKeyword>>;\r\n getBacklinks: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsBacklink>>;\r\n getKeywordDifficulty: (keywords: string[]) => Promise<AhrefsKeywordDifficulty[]>;\r\n getReferringDomains: (\r\n domain: string,\r\n options?: AhrefsPaginationOptions,\r\n ) => Promise<PaginatedResponse<AhrefsReferringDomain>>;\r\n}\r\n\r\nexport interface AhrefsPaginationOptions {\r\n limit?: number;\r\n offset?: number;\r\n}\r\n","// @power-seo/integrations — Shared HTTP Client\n// ----------------------------------------------------------------------------\n\nimport type { HttpClientConfig, HttpClient, AuthStrategy } from './types.js';\nimport { IntegrationApiError } from './types.js';\nimport {\n createTokenBucket,\n consumeToken,\n getWaitTime,\n sleep,\n fetchJsonWithRetry,\n} from '@power-seo/core';\n\nconst DEFAULT_RATE_LIMIT = 600;\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\nfunction buildUrl(\n baseUrl: string,\n path: string,\n params: Record<string, string>,\n auth: AuthStrategy,\n): string {\n const url = new globalThis.URL(`${baseUrl}${path}`);\n for (const [key, value] of Object.entries(params)) {\n url.searchParams.set(key, value);\n }\n if (auth.type === 'query') {\n url.searchParams.set(auth.paramName, auth.value);\n }\n return url.toString();\n}\n\nfunction buildHeaders(auth: AuthStrategy): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n if (auth.type === 'bearer') {\n headers['Authorization'] = `Bearer ${auth.token}`;\n }\n return headers;\n}\n\nfunction getProviderFromUrl(baseUrl: string): string {\n if (baseUrl.includes('semrush')) return 'semrush';\n if (baseUrl.includes('ahrefs')) return 'ahrefs';\n return 'unknown';\n}\n\nexport function createHttpClient(config: HttpClientConfig): HttpClient {\n const maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const bucket = createTokenBucket(config.rateLimitPerMinute ?? DEFAULT_RATE_LIMIT);\n const provider = getProviderFromUrl(config.baseUrl);\n\n async function makeRequest<T>(url: string, init: globalThis.RequestInit): Promise<T> {\n const waitTime = getWaitTime(bucket);\n if (waitTime > 0) {\n await sleep(waitTime);\n }\n consumeToken(bucket);\n\n return fetchJsonWithRetry<T, IntegrationApiError>({\n maxRetries,\n doFetch: async () => {\n const controller = new globalThis.AbortController();\n const timeout = globalThis.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await globalThis.fetch(url, {\n ...init,\n signal: controller.signal,\n });\n } finally {\n globalThis.clearTimeout(timeout);\n }\n },\n buildError: (status, snippet) =>\n new IntegrationApiError(`${provider} API error: ${status} ${snippet}`, status, provider),\n fallbackError: () => new IntegrationApiError('Request failed', 500, provider),\n });\n }\n\n return {\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = buildUrl(config.baseUrl, path, params ?? {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, { method: 'GET', headers });\n },\n\n async post<T>(path: string, body?: unknown): Promise<T> {\n const url = buildUrl(config.baseUrl, path, {}, config.auth);\n const headers = buildHeaders(config.auth);\n return makeRequest<T>(url, {\n method: 'POST',\n headers,\n body: body ? JSON.stringify(body) : undefined,\n });\n },\n };\n}\n","// @power-seo/integrations — Semrush Endpoints\n// ----------------------------------------------------------------------------\n\r\nexport const SEMRUSH_BASE_URL = 'https://api.semrush.com';\r\n\r\nexport function domainOverviewPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function organicKeywordsPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function backlinksPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function keywordDifficultyPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function relatedKeywordsPath(): string {\r\n return '/analytics/v1/';\r\n}\r\n\r\nexport function buildDomainOverviewParams(\r\n domain: string,\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'domain_ranks',\r\n domain,\r\n database,\r\n export_columns: 'Or,Ot,Oc,Ad,At,Ac,Bk,As',\r\n };\r\n}\r\n\r\nexport function buildOrganicKeywordsParams(\r\n domain: string,\r\n database: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n type: 'domain_organic',\r\n domain,\r\n database,\r\n display_limit: String(limit),\r\n display_offset: String(offset),\r\n export_columns: 'Ph,Po,Pp,Nq,Cp,Ur,Tr,Tc,Co,Td',\r\n };\r\n}\r\n\r\nexport function buildBacklinksParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n type: 'backlinks',\r\n target: domain,\r\n target_type: 'root_domain',\r\n display_limit: String(limit),\r\n display_offset: String(offset),\r\n export_columns: 'source_url,target_url,anchor,nofollow,first_seen,last_seen,source_ascore',\r\n };\r\n}\r\n\r\nexport function buildKeywordDifficultyParams(\r\n keywords: string[],\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'phrase_all',\r\n phrase: keywords.join(';'),\r\n database,\r\n export_columns: 'Ph,Kd,Nq,Cp,Co,Nr',\r\n };\r\n}\r\n\r\nexport function buildRelatedKeywordsParams(\r\n keyword: string,\r\n database: string,\r\n): Record<string, string> {\r\n return {\r\n type: 'phrase_related',\r\n phrase: keyword,\r\n database,\r\n export_columns: 'Ph,Nq,Cp,Co,Nr,Rr',\r\n };\r\n}\r\n","// @power-seo/integrations — Semrush Client\n// ----------------------------------------------------------------------------\n\r\nimport type {\r\n SemrushClient,\r\n SemrushDomainOverview,\r\n SemrushKeywordData,\r\n SemrushBacklinkData,\r\n SemrushKeywordDifficulty,\r\n SemrushRelatedKeyword,\r\n PaginatedResponse,\r\n SemrushPaginationOptions,\r\n HttpClientConfig,\r\n} from '../types.js';\r\nimport { createHttpClient } from '../http.js';\r\nimport {\r\n SEMRUSH_BASE_URL,\r\n domainOverviewPath,\r\n organicKeywordsPath,\r\n backlinksPath,\r\n keywordDifficultyPath,\r\n relatedKeywordsPath,\r\n buildDomainOverviewParams,\r\n buildOrganicKeywordsParams,\r\n buildBacklinksParams,\r\n buildKeywordDifficultyParams,\r\n buildRelatedKeywordsParams,\r\n} from './endpoints.js';\r\nimport type {\r\n SemrushRawDomainResponse,\r\n SemrushApiResponse,\r\n SemrushRawKeywordResponse,\r\n SemrushRawBacklinkResponse,\r\n SemrushRawKeywordDifficultyResponse,\r\n SemrushRawRelatedKeywordResponse,\r\n} from './types.js';\r\n\r\nconst DEFAULT_LIMIT = 100;\r\nconst DEFAULT_DATABASE = 'us';\r\n\r\nexport function createSemrushClient(\r\n apiKey: string,\r\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\r\n): SemrushClient {\r\n const http = createHttpClient({\r\n baseUrl: SEMRUSH_BASE_URL,\r\n auth: { type: 'query', paramName: 'key', value: apiKey },\r\n ...config,\r\n });\r\n\r\n return {\r\n async getDomainOverview(domain, database = DEFAULT_DATABASE) {\r\n const params = buildDomainOverviewParams(domain, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawDomainResponse>>(\r\n domainOverviewPath(),\r\n params,\r\n );\r\n const entry = raw.data[0];\r\n return {\r\n domain,\r\n organicKeywords: entry?.Or ?? 0,\r\n organicTraffic: entry?.Ot ?? 0,\r\n organicCost: entry?.Oc ?? 0,\r\n paidKeywords: entry?.Ad ?? 0,\r\n paidTraffic: entry?.At ?? 0,\r\n paidCost: entry?.Ac ?? 0,\r\n backlinks: entry?.Bk ?? 0,\r\n authorityScore: entry?.As ?? 0,\r\n } satisfies SemrushDomainOverview;\r\n },\r\n\r\n async getOrganicKeywords(domain, options?: SemrushPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const database = options?.database ?? DEFAULT_DATABASE;\r\n const params = buildOrganicKeywordsParams(domain, database, limit, offset);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordResponse>>(\r\n organicKeywordsPath(),\r\n params,\r\n );\r\n const data: SemrushKeywordData[] = (raw.data ?? []).map((r) => ({\r\n keyword: r.Ph,\r\n position: r.Po,\r\n previousPosition: r.Pp,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n url: r.Ur,\r\n traffic: r.Tr,\r\n trafficPercent: r.Td,\r\n trafficCost: r.Tc,\r\n competition: r.Co,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<SemrushKeywordData>;\r\n },\r\n\r\n async getBacklinks(domain, options?: SemrushPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildBacklinksParams(domain, limit, offset);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawBacklinkResponse>>(\r\n backlinksPath(),\r\n params,\r\n );\r\n const data: SemrushBacklinkData[] = (raw.data ?? []).map((r) => ({\r\n sourceUrl: r.source_url,\r\n targetUrl: r.target_url,\r\n anchorText: r.anchor,\r\n isNoFollow: r.nofollow,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n sourceAuthorityScore: r.source_ascore,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<SemrushBacklinkData>;\r\n },\r\n\r\n async getKeywordDifficulty(keywords, database = DEFAULT_DATABASE) {\r\n const params = buildKeywordDifficultyParams(keywords, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawKeywordDifficultyResponse>>(\r\n keywordDifficultyPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): SemrushKeywordDifficulty => ({\r\n keyword: r.Ph,\r\n difficulty: r.Kd,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n competition: r.Co,\r\n results: r.Nr,\r\n }),\r\n );\r\n },\r\n\r\n async getRelatedKeywords(keyword, database = DEFAULT_DATABASE) {\r\n const params = buildRelatedKeywordsParams(keyword, database);\r\n const raw = await http.get<SemrushApiResponse<SemrushRawRelatedKeywordResponse>>(\r\n relatedKeywordsPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): SemrushRelatedKeyword => ({\r\n keyword: r.Ph,\r\n searchVolume: r.Nq,\r\n cpc: r.Cp,\r\n competition: r.Co,\r\n results: r.Nr,\r\n relatedTo: r.Rr,\r\n }),\r\n );\r\n },\r\n };\r\n}\r\n","// @power-seo/integrations — Ahrefs Endpoints\n// ----------------------------------------------------------------------------\n\r\nexport const AHREFS_BASE_URL = 'https://api.ahrefs.com/v3';\r\n\r\nexport function siteOverviewPath(): string {\r\n return '/site-explorer/overview';\r\n}\r\n\r\nexport function organicKeywordsPath(): string {\r\n return '/site-explorer/organic-keywords';\r\n}\r\n\r\nexport function backlinksPath(): string {\r\n return '/site-explorer/backlinks';\r\n}\r\n\r\nexport function keywordDifficultyPath(): string {\r\n return '/keywords-explorer/keyword-difficulty';\r\n}\r\n\r\nexport function referringDomainsPath(): string {\r\n return '/site-explorer/referring-domains';\r\n}\r\n\r\nexport function buildSiteOverviewParams(domain: string): Record<string, string> {\r\n return { target: domain, mode: 'domain' };\r\n}\r\n\r\nexport function buildOrganicKeywordsParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n\r\nexport function buildBacklinksParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n\r\nexport function buildKeywordDifficultyParams(keywords: string[]): Record<string, string> {\r\n return {\r\n keywords: keywords.join(','),\r\n };\r\n}\r\n\r\nexport function buildReferringDomainsParams(\r\n domain: string,\r\n limit: number,\r\n offset: number,\r\n): Record<string, string> {\r\n return {\r\n target: domain,\r\n mode: 'domain',\r\n limit: String(limit),\r\n offset: String(offset),\r\n };\r\n}\r\n","// @power-seo/integrations — Ahrefs Client\n// ----------------------------------------------------------------------------\n\r\nimport type {\r\n AhrefsClient,\r\n AhrefsSiteOverview,\r\n AhrefsOrganicKeyword,\r\n AhrefsBacklink,\r\n AhrefsKeywordDifficulty,\r\n AhrefsReferringDomain,\r\n PaginatedResponse,\r\n AhrefsPaginationOptions,\r\n HttpClientConfig,\r\n} from '../types.js';\r\nimport { createHttpClient } from '../http.js';\r\nimport {\r\n AHREFS_BASE_URL,\r\n siteOverviewPath,\r\n organicKeywordsPath,\r\n backlinksPath,\r\n keywordDifficultyPath,\r\n referringDomainsPath,\r\n buildSiteOverviewParams,\r\n buildOrganicKeywordsParams,\r\n buildBacklinksParams,\r\n buildKeywordDifficultyParams,\r\n buildReferringDomainsParams,\r\n} from './endpoints.js';\r\nimport type {\r\n AhrefsRawSiteOverview,\r\n AhrefsApiResponse,\r\n AhrefsRawOrganicKeyword,\r\n AhrefsRawBacklink,\r\n AhrefsRawKeywordDifficulty,\r\n AhrefsRawReferringDomain,\r\n} from './types.js';\r\n\r\nconst DEFAULT_LIMIT = 100;\r\n\r\nexport function createAhrefsClient(\r\n apiToken: string,\r\n config?: Partial<Omit<HttpClientConfig, 'baseUrl' | 'auth'>>,\r\n): AhrefsClient {\r\n const http = createHttpClient({\r\n baseUrl: AHREFS_BASE_URL,\r\n auth: { type: 'bearer', token: apiToken },\r\n ...config,\r\n });\r\n\r\n return {\r\n async getSiteOverview(domain) {\r\n const params = buildSiteOverviewParams(domain);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawSiteOverview>>(\r\n siteOverviewPath(),\r\n params,\r\n );\r\n const entry = raw.data[0];\r\n return {\r\n domain,\r\n domainRating: entry?.domain_rating ?? 0,\r\n urlRating: entry?.url_rating ?? 0,\r\n backlinks: entry?.backlinks ?? 0,\r\n referringDomains: entry?.referring_domains ?? 0,\r\n organicKeywords: entry?.organic_keywords ?? 0,\r\n organicTraffic: entry?.organic_traffic ?? 0,\r\n trafficValue: entry?.traffic_value ?? 0,\r\n } satisfies AhrefsSiteOverview;\r\n },\r\n\r\n async getOrganicKeywords(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildOrganicKeywordsParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawOrganicKeyword>>(\r\n organicKeywordsPath(),\r\n params,\r\n );\r\n const data: AhrefsOrganicKeyword[] = (raw.data ?? []).map((r) => ({\r\n keyword: r.keyword,\r\n position: r.position,\r\n searchVolume: r.volume,\r\n cpc: r.cpc,\r\n url: r.url,\r\n traffic: r.traffic,\r\n trafficPercent: r.traffic_percent,\r\n difficulty: r.difficulty,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsOrganicKeyword>;\r\n },\r\n\r\n async getBacklinks(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildBacklinksParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawBacklink>>(backlinksPath(), params);\r\n const data: AhrefsBacklink[] = (raw.data ?? []).map((r) => ({\r\n sourceUrl: r.url_from,\r\n targetUrl: r.url_to,\r\n anchorText: r.anchor,\r\n domainRating: r.domain_rating,\r\n urlRating: r.url_rating,\r\n isDoFollow: r.is_dofollow,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsBacklink>;\r\n },\r\n\r\n async getKeywordDifficulty(keywords) {\r\n const params = buildKeywordDifficultyParams(keywords);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawKeywordDifficulty>>(\r\n keywordDifficultyPath(),\r\n params,\r\n );\r\n return (raw.data ?? []).map(\r\n (r): AhrefsKeywordDifficulty => ({\r\n keyword: r.keyword,\r\n difficulty: r.difficulty,\r\n searchVolume: r.volume,\r\n cpc: r.cpc,\r\n clicks: r.clicks,\r\n globalVolume: r.global_volume,\r\n }),\r\n );\r\n },\r\n\r\n async getReferringDomains(domain, options?: AhrefsPaginationOptions) {\r\n const limit = options?.limit ?? DEFAULT_LIMIT;\r\n const offset = options?.offset ?? 0;\r\n const params = buildReferringDomainsParams(domain, limit, offset);\r\n const raw = await http.get<AhrefsApiResponse<AhrefsRawReferringDomain>>(\r\n referringDomainsPath(),\r\n params,\r\n );\r\n const data: AhrefsReferringDomain[] = (raw.data ?? []).map((r) => ({\r\n domain: r.domain,\r\n domainRating: r.domain_rating,\r\n backlinks: r.backlinks,\r\n firstSeen: r.first_seen,\r\n lastSeen: r.last_seen,\r\n isDoFollow: r.is_dofollow,\r\n }));\r\n return {\r\n data,\r\n total: raw.total ?? 0,\r\n offset,\r\n limit,\r\n hasMore: data.length === limit,\r\n } satisfies PaginatedResponse<AhrefsReferringDomain>;\r\n },\r\n };\r\n}\r\n"],"mappings":"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","names":["organicKeywordsPath","backlinksPath","keywordDifficultyPath","buildOrganicKeywordsParams","buildBacklinksParams","buildKeywordDifficultyParams","DEFAULT_LIMIT","buildOrganicKeywordsParams","organicKeywordsPath","buildBacklinksParams","backlinksPath","buildKeywordDifficultyParams","keywordDifficultyPath"]}
{
"name": "@power-seo/integrations",
"version": "1.0.15",
"version": "1.0.16",
"description": "Third-party SEO tool API clients for Semrush and Ahrefs with shared HTTP client",

@@ -49,3 +49,3 @@ "keywords": [

"dependencies": {
"@power-seo/core": "1.0.15"
"@power-seo/core": "1.0.16"
},

@@ -52,0 +52,0 @@ "devDependencies": {

+204
-175
# @power-seo/integrations
![integrations banner](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/banner.svg)
![Semrush and Ahrefs API integration banner for the @power-seo/integrations TypeScript package](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/banner.svg)
Query keyword data, domain overviews, backlinks, and keyword difficulty from Semrush and Ahrefs APIs with a shared HTTP client that handles rate limiting and pagination automatically.
Typed Semrush and Ahrefs API clients for keyword research, domain overviews, backlinks, and keyword difficulty — built on a shared rate-limited HTTP client with automatic retry.

@@ -14,8 +14,8 @@ [![npm version](https://img.shields.io/npm/v/@power-seo/integrations)](https://www.npmjs.com/package/@power-seo/integrations)

`@power-seo/integrations` wraps the Semrush and Ahrefs REST APIs with a consistent TypeScript interface. Both clients are built on a shared `createHttpClient()` base that enforces configurable rate limits (requests per window), handles pagination automatically, and normalizes errors into `IntegrationApiError` with status codes and messages. Import only the client you need — tree-shaking ensures unused API code never ends up in your bundle.
`@power-seo/integrations` is a TypeScript client library for the Semrush and Ahrefs SEO data APIs, aimed at developers building keyword research pipelines, backlink monitors, and SEO reporting tools in Node.js. Both clients share one HTTP layer — `createHttpClient()` — that enforces a token-bucket rate limit, retries retryable failures with exponential backoff, and normalizes every non-2xx response into a single `IntegrationApiError` with `status`, `provider`, and `retryable` fields. Every request parameter and response field is fully typed, so keyword volume, CPC, Domain Rating, and backlink data arrive as structured objects instead of hand-parsed payloads.
> **Zero runtime dependencies beyond `fetch`** — runs in Node.js 18+, Deno, Bun, and modern edge runtimes.
>
> ⚠️ **Network Access** — This package makes HTTPS requests to Semrush and Ahrefs APIs. Network access is functionally required and intentional. All requests use your API credentials and are sent only to the configured service endpoints.
![Unified TypeScript interface over Semrush and Ahrefs SEO data APIs](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/header.svg)
> **Network access note** — this package's documented purpose is calling external APIs. HTTPS requests go exclusively to the endpoints you configure (`api.semrush.com`, `api.ahrefs.com`, or your own `baseUrl`), authenticated with your credentials. Nothing else is contacted.
---

@@ -25,20 +25,15 @@

| | Without | With |
| ---------------- | ------------------------- | --------------------------------------------------------------- |
| Semrush API | ❌ Write raw HTTP client | ✅ Typed client with auto-pagination |
| Ahrefs API | ❌ Manual SDK setup | ✅ Typed client with rate limiting |
| Rate limiting | ❌ Manual throttle | ✅ Built-in configurable window rate limiter |
| Pagination | ❌ Manual offset tracking | ✅ Automatic — receive a flat result array |
| Error handling | ❌ Raw HTTP errors | ✅ `IntegrationApiError` with `status`, `provider`, `retryable` |
| TypeScript types | ❌ `any` everywhere | ✅ Full type coverage for all endpoints |
| Bundle size | ❌ Full SDK in bundle | ✅ Tree-shakeable — import only what you use |
| | Without | With |
| ---------------- | --------------------------------------------- | ------------------------------------------------------------- |
| Semrush API | ❌ Hand-write requests, parse column codes | ✅ Typed client — `Or`/`Nq`/`Kd` mapped to named fields |
| Ahrefs API | ❌ Manual bearer auth and query building | ✅ Typed client with token auth handled for you |
| Rate limiting | ❌ Manual throttle or 429 storms | ✅ Built-in token bucket (default 600 requests/minute) |
| Retry logic | ❌ Ad-hoc try/catch loops | ✅ Exponential backoff on 429/5xx, up to 3 retries by default |
| Pagination | ❌ Untyped offset bookkeeping | ✅ `PaginatedResponse<T>` with `offset`, `limit`, `hasMore` |
| Error handling | ❌ Raw HTTP errors, secrets leaking into logs | ✅ `IntegrationApiError` with sanitized, token-redacted body |
| TypeScript types | ❌ `any` everywhere | ✅ Full type coverage for every endpoint and response |
| Bundle size | ❌ Full vendor SDK in your bundle | ✅ Tree-shakeable — import only the client you use |
![Integrations Comparison](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/comparison.svg)
![Workflow comparison showing manual Semrush and Ahrefs API integration steps versus the automated workflow with @power-seo/integrations](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/roi.svg)
<p align="left">
<a href="https://www.buymeacoffee.com/ccbd.dev" target="_blank">
<img src="https://img.buymeacoffee.com/button-api/?text=Buy%20me%20a%20coffee&emoji=&slug=ccbd.dev&button_colour=FFDD00&font_colour=000000&font_family=Cookie&outline_colour=000000&coffee_colour=ffffff" />
</a>
</p>
---

@@ -48,11 +43,12 @@

- **Semrush API client** — domain overview (traffic, organic/paid keywords, backlinks), keyword data (volume, CPC, competition), backlink profile, keyword difficulty, and related keyword suggestions
- **Ahrefs API client** — site overview (Domain Rating, organic traffic), organic keywords with positions, backlink data with anchor text, keyword difficulty, and referring domain list
- **Shared HTTP client** — `createHttpClient()` provides configurable rate limiting (max requests per time window), automatic retry on 429, and JSON response parsing
- **Auto-pagination** — both clients handle multi-page results automatically; callers receive a flat array without manual offset tracking
- **Full TypeScript types** — every request parameter and response field is typed; no `any` in your code
- **Consistent error handling** — `IntegrationApiError` with `status`, `provider`, `message`, and `retryable` flag from both APIs
- **Tree-shakeable** — `createSemrushClient` and `createAhrefsClient` are separate exports; import only what you use
- **Semrush API client** — domain overview (organic/paid traffic, keywords, backlinks, Authority Score), organic keywords with positions, backlink profile, keyword difficulty, and related keyword suggestions
- **Ahrefs API client** — site overview (Domain Rating, URL Rating, traffic value), organic keywords, backlinks with anchor text, keyword difficulty, and referring domains
- **Shared HTTP client** — `createHttpClient()` works against any JSON REST API with query-param or bearer auth, a configurable per-minute rate limit, request timeout, and retry
- **Token-bucket rate limiting** — requests wait for a token instead of failing; the limit is configurable per client via `rateLimitPerMinute`
- **Retry with exponential backoff** — 429 and 5xx responses are retried up to `maxRetries` times with delays of 1s, 2s, 4s… capped at 30s
- **Typed pagination** — list endpoints return `PaginatedResponse<T>` with `data`, `total`, `offset`, `limit`, and `hasMore` for clean cursor loops
- **Sanitized errors** — response bodies embedded in error messages are truncated and have bearer tokens and API keys redacted before they can reach your logs
- **Tree-shakeable, dual ESM + CJS** — `createSemrushClient`, `createAhrefsClient`, and `createHttpClient` are independent named exports
![SEO Research UI](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/seo-research-ui.svg)
![SEO research dashboard powered by typed Semrush and Ahrefs keyword and backlink data](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/seo-research-ui.svg)

@@ -63,15 +59,17 @@ ---

| Feature | @power-seo/integrations | semrush-sdk | ahrefs-client | Custom fetch |
| ------------------------- | :---------------------: | :---------: | :-----------: | :----------: |
| Semrush API client | ✅ | ✅ | ❌ | Manual |
| Ahrefs API client | ✅ | ❌ | Partial | Manual |
| Rate limiting | ✅ | Partial | ❌ | Manual |
| Auto-pagination | ✅ | ❌ | ❌ | Manual |
| Shared HTTP client | ✅ | ❌ | ❌ | — |
| Consistent error handling | ✅ | Partial | ❌ | Manual |
| TypeScript-first | ✅ | ❌ | ❌ | — |
| Tree-shakeable | ✅ | ❌ | ❌ | — |
| Feature | @power-seo/integrations | Hand-rolled `fetch` wrapper | Calling vendor endpoints directly |
| -------------------------------- | :----------------------: | :-------------------------: | :-------------------------------: |
| Typed Semrush responses | ✅ | Manual | ❌ (column codes) |
| Typed Ahrefs responses | ✅ | Manual | ❌ (raw JSON) |
| Rate limiting | ✅ token bucket | Manual | ❌ |
| Retry with backoff | ✅ 429/5xx aware | Manual | ❌ |
| Consistent errors across vendors | ✅ `IntegrationApiError` | Manual | ❌ |
| Secret redaction in errors | ✅ | Rarely | ❌ |
| One HTTP layer for both APIs | ✅ | ❌ | ❌ |
| Tree-shakeable TypeScript | ✅ | — | — |
![Rate Limit Accuracy](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/rate-limit-accuracy.svg)
![Feature comparison matrix of @power-seo/integrations against semrush-sdk, ahrefs-client, and a custom fetch implementation](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/comparison.svg)
![Token-bucket rate limiter keeping request throughput under the configured per-minute API limit](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/rate-limit-accuracy.svg)
---

@@ -93,24 +91,4 @@

---
Requires Node.js 18+ (native `fetch`). Also runs in Deno, Bun, and edge runtimes that provide `fetch`, `URL`, and `AbortController`.
## Quick Start
```ts
import { createSemrushClient, createAhrefsClient } from '@power-seo/integrations';
// Semrush
const semrush = createSemrushClient({ apiKey: process.env.SEMRUSH_API_KEY! });
const overview = await semrush.getDomainOverview({ domain: 'example.com' });
console.log(overview.organicTraffic); // 12_400
console.log(overview.organicKeywords); // 834
// Ahrefs
const ahrefs = createAhrefsClient({ apiKey: process.env.AHREFS_API_KEY! });
const site = await ahrefs.getSiteOverview({ target: 'example.com' });
console.log(site.domainRating); // 47
console.log(site.organicTraffic); // 9_800
```
![Unification Benefit](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/unification-benefit.svg)
---

@@ -120,30 +98,31 @@

### Semrush Client
### How do I query Semrush domain and keyword data in TypeScript?
Create a client with `createSemrushClient(apiKey, config?)` — the first argument is your Semrush API key as a plain string (sent as the `key` query parameter), and the optional second argument tunes `rateLimitPerMinute`, `maxRetries`, and `timeoutMs`. The client exposes five typed methods covering domain overview, organic keywords, backlinks, keyword difficulty, and related keywords. Regional database defaults to `'us'` and list endpoints default to 100 rows per page.
```ts
import { createSemrushClient } from '@power-seo/integrations';
import type { SemrushDomainOverview, SemrushKeywordData } from '@power-seo/integrations';
import type { SemrushDomainOverview } from '@power-seo/integrations';
const semrush = createSemrushClient(
process.env.SEMRUSH_API_KEY!,
{ rateLimitPerMinute: 10, maxRetries: 3 }, // optional
);
const semrush = createSemrushClient(process.env.SEMRUSH_API_KEY!, {
rateLimitPerMinute: 60, // optional — default 600
});
// Domain overview — traffic, keywords, backlinks
// Domain overview — traffic, keywords, backlinks, Authority Score
const overview: SemrushDomainOverview = await semrush.getDomainOverview('example.com', 'us');
// { domain, organicTraffic, paidTraffic, organicKeywords, paidKeywords, backlinks, authorityScore }
// { domain, organicKeywords, organicTraffic, organicCost, paidKeywords,
// paidTraffic, paidCost, backlinks, authorityScore }
// Organic keywords
// Organic keywords (paginated)
const keywords = await semrush.getOrganicKeywords('example.com', { limit: 100, offset: 0 });
// { data: SemrushKeywordData[], total, offset, limit, hasMore }
// Backlinks
// Backlinks (paginated)
const backlinks = await semrush.getBacklinks('example.com', { limit: 100, offset: 0 });
// { data: SemrushBacklinkData[], total, offset, limit, hasMore }
// Keyword difficulty
const difficulty = await semrush.getKeywordDifficulty(['react seo'], 'us');
// Keyword difficulty for a batch of keywords
const difficulty = await semrush.getKeywordDifficulty(['react seo', 'next.js seo'], 'us');
// [{ keyword, difficulty, searchVolume, cpc, competition, results }]
// Related keywords
// Related keyword suggestions
const related = await semrush.getRelatedKeywords('react seo', 'us');

@@ -153,39 +132,57 @@ // [{ keyword, searchVolume, cpc, competition, results, relatedTo }]

### Ahrefs Client
### How do I pull Ahrefs backlinks and Domain Rating?
Create a client with `createAhrefsClient(apiToken, config?)` — the first argument is your Ahrefs API token as a plain string, sent as an `Authorization: Bearer` header to `api.ahrefs.com/v3`. The client covers site overview, organic keywords, backlinks, keyword difficulty, and referring domains. List endpoints accept `{ limit, offset }` and default to 100 rows per page.
```ts
import { createAhrefsClient } from '@power-seo/integrations';
import type { AhrefsSiteOverview, AhrefsOrganicKeyword } from '@power-seo/integrations';
import type { AhrefsSiteOverview } from '@power-seo/integrations';
const ahrefs = createAhrefsClient(
process.env.AHREFS_API_TOKEN!,
{ rateLimitPerMinute: 5, maxRetries: 3 }, // optional
);
const ahrefs = createAhrefsClient(process.env.AHREFS_API_TOKEN!);
// Site overview — DR, organic traffic, backlinks
const overview: AhrefsSiteOverview = await ahrefs.getSiteOverview('example.com');
// { domain, domainRating, urlRating, organicTraffic, organicKeywords, backlinks, referringDomains, trafficValue }
// Site overview — DR, UR, traffic, traffic value
const site: AhrefsSiteOverview = await ahrefs.getSiteOverview('example.com');
// { domain, domainRating, urlRating, backlinks, referringDomains,
// organicKeywords, organicTraffic, trafficValue }
// Organic keywords with positions
// Organic keywords with positions (paginated)
const keywords = await ahrefs.getOrganicKeywords('example.com', { limit: 200, offset: 0 });
// { data: AhrefsOrganicKeyword[], total, offset, limit, hasMore }
// Backlinks with anchor text
const backlinks = await ahrefs.getBacklinks('example.com', { limit: 100, offset: 0 });
// { data: AhrefsBacklink[], total, offset, limit, hasMore }
// Backlinks with anchor text and dofollow status (paginated)
const backlinks = await ahrefs.getBacklinks('example.com', { limit: 100 });
// data[i]: { sourceUrl, targetUrl, anchorText, domainRating, urlRating,
// isDoFollow, firstSeen, lastSeen }
// Keyword difficulty
// Keyword difficulty for a batch of keywords
const kd = await ahrefs.getKeywordDifficulty(['react seo']);
// [{ keyword, difficulty, searchVolume, cpc, clicks, globalVolume }]
// Referring domains
const domains = await ahrefs.getReferringDomains('example.com', { limit: 50, offset: 0 });
// { data: AhrefsReferringDomain[], total, offset, limit, hasMore }
// Referring domains (paginated)
const domains = await ahrefs.getReferringDomains('example.com', { limit: 50 });
```
### Shared HTTP Client
### How do I paginate through all results?
Use the underlying HTTP client directly to call any REST API with rate limiting and pagination:
Every list endpoint returns a `PaginatedResponse<T>` carrying the page you asked for plus the cursor state: `data` (the rows), `total` (as reported by the API), and `offset`, `limit`, `hasMore`. `hasMore` is `true` while a page comes back full, so a simple loop that advances `offset` by `limit` walks the entire result set.
```ts
import type { SemrushKeywordData } from '@power-seo/integrations';
const all: SemrushKeywordData[] = [];
let offset = 0;
let hasMore = true;
while (hasMore) {
const page = await semrush.getOrganicKeywords('example.com', { limit: 100, offset });
all.push(...page.data);
offset += page.limit;
hasMore = page.hasMore;
}
```
### How do I call another REST API with the shared rate-limited client?
`createHttpClient(config)` is exported directly, so the same rate limiting, timeout, retry, and error normalization work against any JSON REST API. Pass a `baseUrl`, an `auth` strategy (`bearer` header or `query` parameter), and optional limits. `get()` appends query params; `post()` JSON-encodes the body. Both return the parsed JSON typed as your generic parameter.
```ts
import { createHttpClient } from '@power-seo/integrations';

@@ -195,14 +192,15 @@

baseUrl: 'https://api.example.com',
auth: { type: 'bearer', token },
rateLimitPerMinute: 60, // max requests per minute
maxRetries: 3,
timeoutMs: 30_000,
auth: { type: 'bearer', token: process.env.API_TOKEN! },
rateLimitPerMinute: 60, // default 600
maxRetries: 3, // default 3
timeoutMs: 30_000, // default 30_000
});
const data = await http.get<MyResponseType>('/endpoint', { query: 'param' });
const result = await http.get<{ items: string[] }>('/endpoint', { q: 'seo' });
const created = await http.post<{ id: string }>('/endpoint', { name: 'report' });
```
### Error Handling
### How do I handle API errors and retries?
Both clients throw `IntegrationApiError` for non-2xx responses:
Every non-2xx response becomes an `IntegrationApiError` with `status` (HTTP status code), `provider` (`'semrush'`, `'ahrefs'`, or `'unknown'`), and `retryable` (`true` for 429 and 5xx). Retryable errors are retried automatically with exponential backoff (1s, 2s, 4s… capped at 30s) up to `maxRetries` times before being thrown; non-retryable errors (4xx other than 429) throw immediately. Error messages embed a sanitized response snippet — bearer tokens and API keys are redacted and the snippet is truncated to 200 characters.

@@ -212,3 +210,3 @@ ```ts

const semrush = createSemrushClient({ apiKey: 'your-key' });
const semrush = createSemrushClient(process.env.SEMRUSH_API_KEY!);

@@ -219,7 +217,6 @@ try {

if (err instanceof IntegrationApiError) {
console.error(`API error ${err.status}: ${err.message}`);
console.error('Provider:', err.provider);
console.error('Retryable:', err.retryable);
console.error(`${err.provider} API error ${err.status}: ${err.message}`);
console.error('Retryable:', err.retryable); // true for 429 / 5xx
} else {
throw err;
throw err; // network failure or abort — propagated without retry
}

@@ -229,2 +226,4 @@ }

![One shared HTTP client unifying auth, rate limiting, retry, and errors for both SEO API vendors](https://raw.githubusercontent.com/CyberCraftBD/power-seo/main/image/integrations/unification-benefit.svg)
---

@@ -234,3 +233,3 @@

### Semrush
### `createSemrushClient(apiKey, config?)`

@@ -244,13 +243,13 @@ ```ts

#### `SemrushClient` methods
Targets `https://api.semrush.com` with query-parameter auth (`key`).
| Method | Parameters | Returns | Description |
| ---------------------- | ------------------------- | ---------------------------------------- | ------------------------------------ |
| `getDomainOverview` | `(domain, database?)` | `SemrushDomainOverview` | Traffic, keywords, backlinks summary |
| `getOrganicKeywords` | `(domain, options?)` | `PaginatedResponse<SemrushKeywordData>` | Keywords with rankings |
| `getBacklinks` | `(domain, options?)` | `PaginatedResponse<SemrushBacklinkData>` | Backlinks with source/target URLs |
| `getKeywordDifficulty` | `(keywords[], database?)` | `SemrushKeywordDifficulty[]` | KD scores with volume/CPC |
| `getRelatedKeywords` | `(keyword, database?)` | `SemrushRelatedKeyword[]` | Related keyword suggestions |
| Method | Parameters | Returns | Notes |
| ---------------------- | ------------------------- | ------------------------------------------------- | --------------------------------- |
| `getDomainOverview` | `(domain, database?)` | `Promise<SemrushDomainOverview>` | `database` defaults to `'us'` |
| `getOrganicKeywords` | `(domain, options?)` | `Promise<PaginatedResponse<SemrushKeywordData>>` | `limit` defaults to 100 |
| `getBacklinks` | `(domain, options?)` | `Promise<PaginatedResponse<SemrushBacklinkData>>` | Root-domain backlink profile |
| `getKeywordDifficulty` | `(keywords[], database?)` | `Promise<SemrushKeywordDifficulty[]>` | Batch lookup, one row per keyword |
| `getRelatedKeywords` | `(keyword, database?)` | `Promise<SemrushRelatedKeyword[]>` | Related keyword suggestions |
### Ahrefs
### `createAhrefsClient(apiToken, config?)`

@@ -264,13 +263,13 @@ ```ts

#### `AhrefsClient` methods
Targets `https://api.ahrefs.com/v3` with `Authorization: Bearer` auth.
| Method | Parameters | Returns | Description |
| ---------------------- | -------------------- | ------------------------------------------ | ------------------------------- |
| `getSiteOverview` | `(domain)` | `AhrefsSiteOverview` | DR, organic traffic, backlinks |
| `getOrganicKeywords` | `(domain, options?)` | `PaginatedResponse<AhrefsOrganicKeyword>` | Ranking keywords with positions |
| `getBacklinks` | `(domain, options?)` | `PaginatedResponse<AhrefsBacklink>` | Backlinks with anchor text |
| `getKeywordDifficulty` | `(keywords[])` | `AhrefsKeywordDifficulty[]` | KD scores with volume/CPC |
| `getReferringDomains` | `(domain, options?)` | `PaginatedResponse<AhrefsReferringDomain>` | Referring domains by DR |
| Method | Parameters | Returns | Notes |
| ---------------------- | -------------------- | --------------------------------------------------- | ------------------------------ |
| `getSiteOverview` | `(domain)` | `Promise<AhrefsSiteOverview>` | DR, UR, traffic, traffic value |
| `getOrganicKeywords` | `(domain, options?)` | `Promise<PaginatedResponse<AhrefsOrganicKeyword>>` | `limit` defaults to 100 |
| `getBacklinks` | `(domain, options?)` | `Promise<PaginatedResponse<AhrefsBacklink>>` | Anchor text + dofollow status |
| `getKeywordDifficulty` | `(keywords[])` | `Promise<AhrefsKeywordDifficulty[]>` | Batch lookup |
| `getReferringDomains` | `(domain, options?)` | `Promise<PaginatedResponse<AhrefsReferringDomain>>` | Referring domains with DR |
### Shared
### `createHttpClient(config)`

@@ -281,39 +280,62 @@ ```ts

`HttpClient` has two methods: `get<T>(path, params?)` and `post<T>(path, body?)`.
#### `HttpClientConfig`
| Prop | Type | Default | Description |
| -------------------- | -------------- | ------- | -------------------------------------------- |
| `baseUrl` | `string` | — | Base URL for all requests |
| `auth` | `AuthStrategy` | — | Authentication: bearer token or query |
| `rateLimitPerMinute` | `number` | — | Optional: max requests per minute |
| `maxRetries` | `number` | — | Optional: retry failed requests (default: 3) |
| `timeoutMs` | `number` | — | Optional: request timeout in milliseconds |
| Prop | Type | Default | Description |
| -------------------- | -------------- | -------- | -------------------------------------------------- |
| `baseUrl` | `string` | required | Base URL prepended to every request path |
| `auth` | `AuthStrategy` | required | `bearer` header or `query` parameter credential |
| `rateLimitPerMinute` | `number` | `600` | Token-bucket capacity; requests wait for a token |
| `maxRetries` | `number` | `3` | Retries after the first attempt (429/5xx only) |
| `timeoutMs` | `number` | `30000` | Per-request timeout enforced via `AbortController` |
**AuthStrategy** is one of:
`AuthStrategy` is one of:
- `{ type: 'bearer', token: string }` — Authorization header
- `{ type: 'query', paramName: string, value: string }` — Query parameter auth
- `{ type: 'bearer', token: string }` — sent as an `Authorization: Bearer` header
- `{ type: 'query', paramName: string, value: string }` — appended as a query parameter
### Types
### `IntegrationApiError`
| Property | Type | Description |
| ----------- | --------- | -------------------------------------------------- |
| `message` | `string` | Provider, status, and a sanitized response snippet |
| `status` | `number` | HTTP status code of the failed response |
| `provider` | `string` | `'semrush'`, `'ahrefs'`, or `'unknown'` |
| `retryable` | `boolean` | `true` when `status === 429` or `status >= 500` |
---
## Types
All exported types, importable with `import type`:
```ts
import type {
// HTTP layer
AuthStrategy,
HttpClientConfig,
HttpClient,
PaginatedResponse,
SemrushConfig,
// Semrush
SemrushClient,
SemrushPaginationOptions, // { limit?, offset?, database? }
SemrushDomainOverview,
SemrushKeywordData,
SemrushBacklinkData,
SemrushKeywordDifficulty,
SemrushRelatedKeyword,
SemrushClient,
AhrefsConfig,
// Ahrefs
AhrefsClient,
AhrefsPaginationOptions, // { limit?, offset? }
AhrefsSiteOverview,
AhrefsOrganicKeyword,
AhrefsBacklink,
AhrefsKeywordDifficulty,
AhrefsReferringDomain,
AhrefsClient,
} from '@power-seo/integrations';
```
`PaginatedResponse<T>` is `{ data: T[]; total: number; offset: number; limit: number; hasMore: boolean }`. The `IntegrationApiError` class is a value export: `import { IntegrationApiError } from '@power-seo/integrations'`.
---

@@ -323,7 +345,8 @@

- **Keyword research pipelines** — pull volume and difficulty from Semrush to prioritize content creation
- **Backlink monitoring** — automate periodic Ahrefs backlink snapshots for link building tracking
- **Competitor analysis** — use domain overview data to compare your metrics against competitors
- **Content brief generation** — combine keyword difficulty and volume to prioritize blog topics
- **SEO reporting dashboards** — pull live Semrush/Ahrefs data into internal analytics tools built with `@power-seo/analytics`
- **Keyword research pipelines** — pull volume, CPC, and difficulty from Semrush or Ahrefs to prioritize content topics programmatically
- **Backlink monitoring** — schedule periodic backlink and referring-domain snapshots for link-building tracking
- **Competitor analysis** — compare domain overview metrics (traffic, keywords, Authority Score, Domain Rating) across competitor domains
- **Content brief generation** — feed related-keyword and difficulty data into briefs alongside [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) scoring
- **SEO reporting dashboards** — combine live vendor data with Search Console trends from [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) and [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console)
- **Any rate-limited REST integration** — reuse `createHttpClient()` for internal or third-party JSON APIs that need throttling and retry

@@ -334,9 +357,8 @@ ---

- **Pure TypeScript** — no compiled binary, no native modules
- **Minimal runtime dependencies** — only native `fetch` (available in Node 18+, Deno, Bun, and all Edge runtimes)
- **Framework-agnostic** — works in Next.js API routes, Remix loaders, Express, Cloudflare Workers
- **SSR compatible** — safe for server-side use; no browser-specific APIs
- **Edge runtime safe** — uses only `fetch`; runs in Cloudflare Workers, Vercel Edge, Deno
- **Tree-shakeable** — `createSemrushClient` and `createAhrefsClient` are separate named exports
- **Dual ESM + CJS** — ships both formats via tsup for any bundler or `require()` usage
- **Pure TypeScript** — no native modules, no compiled binaries
- **Runtime dependency: `@power-seo/core` only** — the token bucket (`createTokenBucket`), backoff (`calculateBackoff`), and retry loop (`fetchJsonWithRetry`) come from the core package; nothing third-party
- **Native `fetch` transport** — works in Node.js 18+, Deno, Bun, Cloudflare Workers, and Vercel Edge; timeouts use `AbortController`
- **Thin vendor adapters** — each client maps raw vendor responses (Semrush column codes like `Or`, `Nq`, `Kd`; Ahrefs snake_case fields) to stable camelCase types; the HTTP concerns live in one place
- **Secret-safe errors** — response snippets in error messages pass through redaction that strips bearer tokens and API-key-shaped JSON fields before truncating to 200 characters
- **Tree-shakeable, dual ESM + CJS** — `sideEffects: false`, separate named exports, built with tsup

@@ -347,6 +369,7 @@ ---

- Published to npm with **provenance attestation** — every release is built and signed by the verified `github.com/CyberCraftBD/power-seo` GitHub Actions workflow, so you can trace each tarball back to its exact source commit
- **Zero third-party runtime dependencies** — packages depend only on other `@power-seo` packages, nothing else gets pulled in
- **Network access only when you call it** — HTTP requests go exclusively to the APIs you configure (Google Search Console / Semrush / Ahrefs); no telemetry, no phoning home
- No install scripts (`postinstall`, `preinstall`)
- No runtime network access beyond explicit API calls you initiate
- No `eval` or dynamic code execution
- CI-signed builds — all releases published via verified `github.com/CyberCraftBD/power-seo` workflow
- Safe for SSR, Edge, and server environments

@@ -360,24 +383,30 @@

| Package | Install | Description |
| ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------- |
| [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants |
| [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs |
| [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR |
| [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 23 builders + 22 React components |
| [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components |
| [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
| [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators |
| [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation |
| [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters |
| [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis — orphan detection, suggestions, equity scoring |
| [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine — meta, content, structure, performance rules |
| [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO — alt text, lazy loading, format analysis, image sitemaps |
| [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks |
| [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard |
| [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection |
| [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination |
| [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management |
| Package | Install | Description |
| ------------------------------------------------------------------------------------------ | ----------------------------------- | ---------------------------------------------------------------------------------- |
| [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic prompt templates and response parsers for AI-assisted SEO |
| [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge Search Console data with audit results — trends and ranking insights |
| [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | SEO site health auditing with meta, content, structure, and performance rules |
| [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content analysis engine with scoring, checks, and React components |
| [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic SEO analysis engines, types, validators, and utilities |
| [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO analysis — alt text quality, lazy loading, formats, image sitemaps |
| [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with a shared rate-limited HTTP client |
| [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Internal link graph analysis — orphan detection, suggestions, equity scoring |
| [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta tag helpers for Next.js App Router, Remix v2, and generic SSR |
| [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter Card preview generators with React components |
| [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta tags, Open Graph, Twitter Card, breadcrumbs |
| [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI |
| [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect rule engine with Next.js, Remix, and Express adapters |
| [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 23 schema.org builders plus React components |
| [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API client — OAuth2, service accounts, rate limiting, retry |
| [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, and validation with image, video, news support |
| [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | Analytics script builders with consent management and React components |
---
## Keywords
semrush api, ahrefs api, seo api client, keyword research api, backlink api, domain overview, keyword difficulty, keyword volume, rate limiting, token bucket, api pagination, typescript seo, seo data fetching, third-party seo integrations, referring domains, organic keywords, api retry backoff, seo reporting
---
## About [CyberCraft Bangladesh](https://ccbd.dev)

@@ -384,0 +413,0 @@