Skip to main content

DataForSEO Integration

Complete guide to DataForSEO API integration, rate limits, cost management, and usage patterns.


API Overview​

RankFabric integrates with multiple DataForSEO API products to provide comprehensive SEO and competitive intelligence data:

API ProductPurposeBase Cost
Backlinks APIReferring domains, backlink analysis, spam scores$0.02-0.10/request
DataForSEO LabsKeyword research, ranked keywords, domain metrics$0.004-0.05/request
Keywords Data APIKeyword suggestions from Google Ads$0.05/request
On-Page APIInstant page content fetching$0.000125/URL
App Data APIMobile app store data (Google Play, Apple)$0.01-0.05/request
Business Data APIBusiness listings searchVariable

File Structure​

packages/api/src/lib/dataforseo/
├── dataforseo-api.js # App Data API (Google Play, Apple App Store)
├── dataforseo.js # Keywords API (Labs + Google Ads)
├── dataforseo-backlinks.js # Backlinks API (referring domains, summaries)
├── dataforseo-spam-score.js # Bulk spam scores and domain ranks
├── dataforseo-categories.js # Category taxonomy mapping
├── dataforseo-listings.js # App listings search
├── dataforseo-business.js # Business listings search
└── instant-pages-batch.js # On-Page instant pages (batched)

Authentication​

Credential Storage​

DataForSEO uses HTTP Basic Authentication. Credentials are stored as Cloudflare Worker secrets:

# Set credentials via Wrangler CLI
echo "your-login" | wrangler secret put DATAFORSEO_LOGIN
echo "your-password" | wrangler secret put DATAFORSEO_PASSWORD

Authentication Implementation​

All DataForSEO modules use centralized authentication from dataforseo-api.js:

// Centralized auth function - import this instead of duplicating
export function getDataForSEOAuth(env) {
if (!env.DATAFORSEO_LOGIN || !env.DATAFORSEO_PASSWORD) {
throw new Error("DATAFORSEO_LOGIN or DATAFORSEO_PASSWORD missing");
}
return `Basic ${btoa(`${env.DATAFORSEO_LOGIN}:${env.DATAFORSEO_PASSWORD}`)}`;
}

// Usage in API calls
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: getDataForSEOAuth(env),
},
body: JSON.stringify(payload),
});

Environment Variables​

Configure in wrangler.toml:

[vars]
DATAFORSEO_LABS_LIMIT = "100"
DATAFORSEO_LABS_MAX_REQUESTS = "1"
DATAFORSEO_DAILY_BUDGET = "50"
DATAFORSEO_LOCATION_CODE = "2840"
DATAFORSEO_LANGUAGE_CODE = "en"

The Backlinks API provides link intelligence data including referring domains, individual backlinks, domain summaries, and spam scores.

Referring Domains​

Get domains linking to a target with full metrics.

Endpoint: POST https://api.dataforseo.com/v3/backlinks/referring_domains/live

Cost: $0.02 base + $0.0001 per result

Request:

const params = {
target: "spotify.com",
limit: 1000,
offset: 0,
order_by: ["rank,desc"],
include_subdomains: true,
exclude_internal_backlinks: true,
include_indirect_links: true,
backlinks_status_type: "live",
internal_list_limit: 10,
rank_scale: "one_hundred",
};

const response = await backlinksRequest("/referring_domains/live", [params], env);

Response Structure:

{
"tasks": [{
"status_code": 20000,
"cost": 0.12,
"result": [{
"target": "spotify.com",
"total_count": 5847293,
"items_count": 1000,
"items": [{
"domain": "example.com",
"rank": 85,
"backlinks": 47,
"backlinks_spam_score": 12,
"referring_domains": 1250,
"broken_backlinks": 2,
"first_seen": "2019-02-16 14:22:37 +00:00",
"lost_date": null,
"referring_links_tld": { "com": 35, "org": 8, "net": 4 },
"referring_links_types": { "anchor": 40, "image": 5, "redirect": 2 },
"referring_links_platform_types": { "blogs": 15, "news": 10 },
"referring_links_semantic_locations": { "main": 30, "footer": 10 },
"referring_links_countries": { "US": 25, "GB": 12, "DE": 5 }
}]
}]
}]
}

Usage:

import { getReferringDomains, fetchAndStoreReferringDomains } from "./dataforseo-backlinks.js";

// Fetch only
const result = await getReferringDomains("spotify.com", { limit: 1000 }, env);

// Fetch and store in database
const stored = await fetchAndStoreReferringDomains("spotify.com", {
limit: 1000,
dataTier: "global_sample",
}, env);
// Returns: { total_count, fetched, inserted, updated, cost }

Domain Summary​

Aggregate backlink statistics without individual domain details.

Endpoint: POST https://api.dataforseo.com/v3/backlinks/summary/live

Cost: $0.02 per request (cheap aggregate endpoint)

Request:

const params = {
target: "spotify.com",
include_subdomains: true,
exclude_internal_backlinks: true,
backlinks_status_type: "live",
};

Response:

{
"tasks": [{
"cost": 0.02,
"result": [{
"target": "spotify.com",
"rank": 92,
"backlinks": 847293847,
"referring_domains": 5847293,
"referring_domains_nofollow": 1234567,
"referring_main_domains": 4521000,
"referring_ips": 892345,
"referring_subnets": 234567,
"backlinks_spam_score": 8,
"broken_backlinks": 12847,
"broken_pages": 4521,
"referring_links_types": { "anchor": 70, "image": 20, "redirect": 10 },
"referring_links_platform_types": {
"news": 150000,
"blogs": 280000,
"ecommerce": 45000,
"message-boards": 32000,
"social": 89000
}
}]
}]
}

Usage:

import { getDomainSummary, fetchAndStoreDomainSummary } from "./dataforseo-backlinks.js";

// Fetch and store with weekly snapshots
const result = await fetchAndStoreDomainSummary("spotify.com", {}, env);
// Stores in domain_summaries table with year_week for historical tracking

Get actual URLs linking to the target.

Endpoint: POST https://api.dataforseo.com/v3/backlinks/backlinks/live

Cost: $0.02 base + $0.0001 per result

Request:

const params = {
target: "spotify.com",
limit: 1000,
offset: 0,
order_by: ["rank,desc"],
include_subdomains: true,
exclude_internal_backlinks: true,
backlinks_status_type: "live",
mode: "as_is", // as_is, one_per_domain, one_per_anchor
};

Response includes:

  • url_from - Source URL of the backlink
  • url_to - Target URL being linked to
  • anchor - Anchor text
  • domain_from_rank - Source domain authority
  • page_from_rank - Source page authority
  • backlink_spam_score - Spam score (0-100)
  • semantic_location - Where link appears (main, footer, sidebar)
  • link_attributes - nofollow, ugc, sponsored
  • is_broken, is_lost - Link status

Domain Pages Summary​

Get backlink stats for pages on YOUR domain (not linking domains).

Endpoint: POST https://api.dataforseo.com/v3/backlinks/domain_pages_summary/live

Cost: $0.02 base + $0.0001 per result

Usage:

import { getDomainPagesSummary, fetchAndStoreDomainPagesSummary } from "./dataforseo-backlinks.js";

// Get backlink stats for top pages on your domain
const result = await getDomainPagesSummary("notion.so", { limit: 500 }, env);
// Returns pages sorted by backlinks count with spam scores and link distributions

Bulk Spam Scores​

Fetch spam scores for up to 1000 targets in one request.

Endpoint: POST https://api.dataforseo.com/v3/backlinks/bulk_spam_score/live

Cost: ~$0.02 per 1000 targets

Request:

const targets = ["forbes.com", "spamsite.xyz", "example.com"];

const response = await fetch("https://api.dataforseo.com/v3/backlinks/bulk_spam_score/live", {
method: "POST",
headers: {
Authorization: `Basic ${authB64}`,
"Content-Type": "application/json",
},
body: JSON.stringify([{ targets }]),
});

Response:

{
"tasks": [{
"cost": 0.02,
"result": [{
"items": [
{ "target": "forbes.com", "spam_score": 2 },
{ "target": "spamsite.xyz", "spam_score": 87 },
{ "target": "example.com", "spam_score": 5 }
]
}]
}]
}

Spam Score Tiers:

ScoreTierDescription
0-19cleanLow risk, high quality
20-39low_riskAcceptable
40-59moderateMonitor
60-79high_riskCaution advised
80-100toxicDisavow candidate

Bulk Domain Ranks​

Fetch domain authority ranks for up to 1000 domains.

Endpoint: POST https://api.dataforseo.com/v3/backlinks/bulk_ranks/live

Cost: ~$0.02 per 1000 targets

import { fetchBulkRanks, enrichDomainWithRank } from "./dataforseo-spam-score.js";

// Batch fetch
const { results, cost } = await fetchBulkRanks(["google.com", "example.com"], env);
// results = { "google.com": 100, "example.com": 45 }

// Fetch and update database with quality_tier
await enrichDomainWithRank(domainId, "example.com", env);

Keywords API​

Ranked Keywords​

Get keywords a domain actually ranks for with positions and URLs.

Endpoint: POST https://api.dataforseo.com/v3/dataforseo_labs/google/ranked_keywords/live

Cost: $0.05 per request

Request:

const payload = [{
target: "notion.so",
location_code: 2840,
language_code: "en",
limit: 100,
offset: 0,
order_by: ["keyword_data.keyword_info.search_volume,desc"],
load_rank_absolute: true,
ignore_synonyms: false,
include_clickstream_data: false,
}];

Response:

{
"tasks": [{
"cost": 0.05,
"result": [{
"total_count": 847293,
"items": [{
"keyword_data": {
"keyword": "notion templates",
"keyword_info": {
"search_volume": 27100,
"cpc": 2.45,
"competition": 0.65,
"monthly_searches": [
{ "month": 11, "year": 2025, "search_volume": 27100 }
]
},
"search_intent_info": {
"main_intent": "commercial"
},
"keyword_properties": {
"keyword_difficulty": 72
}
},
"ranked_serp_element": {
"url": "https://notion.so/templates",
"serp_item": {
"rank_absolute": 3,
"rank_group": 2,
"is_new": false,
"is_up": true
}
}
}]
}]
}]
}

Usage:

import { fetchRankedKeywords, fetchAllRankedKeywords } from "./dataforseo.js";

// Single page
const result = await fetchRankedKeywords("notion.so", env, { limit: 100 });

// Paginated fetch (up to max_items)
const all = await fetchAllRankedKeywords("notion.so", env, {
max_items: 1000,
batch_size: 100,
});
// Returns: { items, total_count, fetched, cost }

Keywords for Site​

Get keyword opportunities for a domain.

Endpoint: POST https://api.dataforseo.com/v3/dataforseo_labs/google/keywords_for_site/live

Cost: ~$0.004 per 100 keywords

import { fetchKeywordsForSite } from "./dataforseo.js";

const keywords = await fetchKeywordsForSite("notion.so", env, {
limit: 100,
location_code: 2840,
language_code: "en",
});

Keyword Suggestions (Labs)​

Get related keywords with full metrics.

Endpoint: POST https://api.dataforseo.com/v3/dataforseo_labs/google/keyword_suggestions/live

Cost: ~$0.004 per 100 keywords

import { fetchKeywordSuggestionsLabs } from "./dataforseo.js";

const result = await fetchKeywordSuggestionsLabs({
keyword: "project management",
location_code: 2840,
language_code: "en",
limit: 100,
include_seed_keyword: true,
include_serp_info: false,
order_by: ["keyword_info.search_volume,desc"],
}, env);

// Returns: { suggestions, meta, raw }

Keyword Suggestions (Google Ads)​

Get related keywords from Google Ads API.

Endpoint: POST https://api.dataforseo.com/v3/keywords_data/google_ads/keywords_for_keywords/live

Cost: ~$0.05 per request

import { fetchKeywordSuggestions } from "./dataforseo.js";

const suggestions = await fetchKeywordSuggestions("seo tools", env, {
limit: 100,
location_code: 2840,
include_seed_keyword: true,
});

Categories for Domain​

Get DataForSEO category IDs for a domain based on its keyword rankings.

Endpoint: POST https://api.dataforseo.com/v3/dataforseo_labs/google/categories_for_domain/live

import { fetchCategoriesForDomain } from "./dataforseo.js";

const categories = await fetchCategoriesForDomain("notion.so", env, {
limit: 100,
include_subcategories: false,
});
// Returns categories with organic/paid ETV and keyword counts

Domain Metrics by Category​

Get top domains for a specific category with traffic metrics.

Endpoint: POST https://api.dataforseo.com/v3/dataforseo_labs/google/domain_metrics_by_categories/live

import { fetchDomainMetricsByCategory } from "./dataforseo.js";

const domains = await fetchDomainMetricsByCategory(12045, env, {
limit: 100,
first_date: "2025-09-01",
second_date: "2025-11-01",
});
// Returns domains with organic ETV, keyword counts, and period-over-period changes

On-Page API (Instant Pages)​

Fetch rendered HTML content from URLs with metadata extraction.

Rate Limits​

  • 2,000 requests/minute
  • 20 tasks (URLs) per request
  • Max 5 same-domain URLs per batch
  • 30 concurrent requests

Theoretical throughput: 40,000 URLs/minute

Costs​

  • Cost per URL: $0.000125

Batched Requests​

import { fetchInstantPagesBatch, fetchInstantPagesSingle } from "./instant-pages-batch.js";

// Batch fetch (recommended)
const results = await fetchInstantPagesBatch(
["https://example.com/page1", "https://example.com/page2"],
env,
{
enable_javascript: true,
enable_browser_rendering: false,
load_resources: false,
store_raw_html: false,
}
);
// Returns: Map<url, result>

// Single URL convenience wrapper
const result = await fetchInstantPagesSingle("https://example.com", env);

Request Payload:

[{
"url": "https://example.com/page",
"check_spell": false,
"disable_cookie_popup": true,
"return_despite_timeout": true,
"load_resources": false,
"enable_javascript": true,
"enable_browser_rendering": false,
"store_raw_html": false
}]

Response Structure:

{
success: true,
url: "https://example.com/page",
status_code: 200,
content: {
title: "Page Title",
description: "Meta description",
h1: ["Main Heading"],
h2: ["Subheading 1", "Subheading 2"],
h3: [],
plain_text: "Full text content...",
word_count: 1500,
internal_links_count: 25,
external_links_count: 8,
},
raw: {
canonical: "https://example.com/page",
og_tags: { "og:title": "...", "og:image": "..." },
scripts: ["https://cdn.example.com/app.js"],
links: { internal: [...], external: [...] },
content_type: "text/html",
charset: "utf-8",
raw_html: null, // Only if store_raw_html: true
},
cost: 0.000125,
}

App Data API​

Google Play App List​

Get apps from Google Play charts by category.

Endpoint: POST https://api.dataforseo.com/v3/app_data/google/app_list/task_post

import { getGooglePlayCategoryApps } from "./dataforseo-api.js";

const task = await getGooglePlayCategoryApps("PRODUCTIVITY", {
appCollection: "topselling_free", // topselling_free, topselling_paid, topgrossing
locationCode: 2840,
depth: 100,
priority: 1, // 1 = normal (45 min), 2 = high (1 min)
}, env);
// Returns: { task_id, cost, status_code }

Apple App Store​

import { getAppleCategoryApps, getAppleAppInfo } from "./dataforseo-api.js";

// Category list
const task = await getAppleCategoryApps("6007", { // Productivity category
appCollection: "top_free_ios",
depth: 100,
}, env);

// Individual app info
const appTask = await getAppleAppInfo("284882215", env); // Facebook app ID

App Listings Search (Live)​

Instant results for app searches by category.

import { searchGooglePlayListings, searchAppleListings } from "./dataforseo-listings.js";

// Google Play
const result = await searchGooglePlayListings("Productivity", {
limit: 100,
appCollection: "topselling_free",
}, env);

// Apple
const result = await searchAppleListings(["Productivity"], {
limit: 100,
locationCode: 2840,
}, env);

Rate Limiting​

Account Limits​

Limit TypeValue
Requests per minute2,000
Concurrent requests30
Tasks per requestVaries by endpoint (1-1000)
Daily requestsUnlimited (cost-based)

Handling Rate Limits​

// Rate limit error response
{
"status_code": 40901,
"status_message": "Rate limit exceeded"
}

// Retry with exponential backoff
async function retryWithBackoff(fn, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (err) {
if (err.message.includes("Rate limit") && i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000 + Math.random() * 1000;
await new Promise(res => setTimeout(res, delay));
continue;
}
throw err;
}
}
}

Cost Management​

Per-Call Costs​

EndpointBase CostPer-Result Cost
Backlinks - Referring Domains$0.02$0.0001/domain
Backlinks - Summary$0.02-
Backlinks - Backlinks$0.02$0.0001/link
Backlinks - Bulk Spam Score$0.02Per 1000 targets
Backlinks - Bulk Ranks$0.02Per 1000 targets
Labs - Ranked Keywords$0.05-
Labs - Keywords for Site~$0.004Per 100 keywords
Labs - Keyword Suggestions~$0.004Per 100 keywords
Keywords Data - Keywords for Keywords$0.05-
On-Page - Instant Pages$0.000125Per URL
App Data - App List$0.01-0.05Varies

Cost Tracking​

All API calls track costs via centralized cost tracker:

import { trackCost, COST_SERVICES } from "../utils/cost-tracker.js";

// Track after successful API call
if (task.cost > 0) {
await trackCost(env, {
service: COST_SERVICES.DATAFORSEO_BACKLINKS,
cost_usd: task.cost,
endpoint: "/referring_domains/live",
items_returned: result.items_count || 0,
success: true,
});
}

Budget Enforcement​

// Check daily budget before expensive operations
const spent = await env.DFS_BUDGETS.get('budget:daily');
const limit = parseFloat(env.DATAFORSEO_DAILY_BUDGET || 50);

if (parseFloat(spent || 0) >= limit) {
throw new Error('Daily DataForSEO budget exceeded');
}

Cost Optimization Best Practices​

  1. Use batch endpoints - 100 keywords in 1 request = $0.004 vs 100 separate requests = $0.40

  2. Use bulk endpoints - Bulk spam scores/ranks: 1000 targets for $0.02 vs individual lookups

  3. Use summary endpoints - Domain summary ($0.02) vs full referring domains ($0.02 + $0.0001/result)

  4. Cache aggressively - Category data rarely changes, keyword data changes monthly

  5. Set daily budgets - Prevent runaway costs from bugs or loops


Error Handling​

Status Codes​

CodeMeaningAction
20000SuccessProcess results
20100Task in queuePoll for results
40001Invalid parametersFix request payload
40101Authentication failedCheck credentials
40201Rate limit exceededImplement backoff
40204Insufficient balanceAdd credits
50000Internal server errorRetry with backoff

Error Response Example​

{
"status_code": 40101,
"status_message": "Incorrect login or password",
"tasks": null
}

Error Handling Pattern​

async function makeDataForSEORequest(endpoint, payload, env) {
const response = await fetch(endpoint, {
method: "POST",
headers: {
Authorization: getDataForSEOAuth(env),
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});

if (!response.ok) {
const errorText = await response.text();
const err = new Error(
`DataForSEO API error ${response.status}: ${errorText.slice(0, 200)}`
);

// Set appropriate error status for upstream handling
if (response.status === 429) {
err.status = 429; // Rate limit - retry
} else if (response.status >= 400 && response.status < 500) {
err.status = 400; // Bad request - don't retry
} else {
err.status = 502; // Server error - retry
}
throw err;
}

const data = await response.json();

if (data.status_code !== 20000) {
throw new Error(`DataForSEO error: ${data.status_message} (code: ${data.status_code})`);
}

const task = data.tasks?.[0];
if (!task || task.status_code !== 20000) {
throw new Error(`DataForSEO task error: ${task?.status_message || "No task returned"}`);
}

return task;
}

Retry Strategy​

Error TypeRetry?Strategy
Network errorsYesExponential backoff
5xx server errorsYesExponential backoff
Rate limit (429/40201)YesLonger backoff (5-30s)
401 authenticationNoCheck credentials
400 bad requestNoFix request
402 insufficient fundsNoAdd credits

Caching​

Cache Strategy by Data Type​

Data TypeTTLCache Key Pattern
Category taxonomy30 daysdfs:category:{id}
Domain summary7 daysdfs:summary:{domain}:{yearWeek}
Keyword suggestions7 daysdfs:suggestions:{keyword}:{location}
Ranked keywords1 daydfs:ranked:{domain}:{location}
Spam scores7 daysdfs:spam:{domain}
Instant pages1 daydfs:page:{urlHash}

KV Cache Implementation​

// Check cache before API call
const cacheKey = `dfs:suggestions:${keyword}:${locationCode}`;
const cached = await env.DFS_CACHE.get(cacheKey);

if (cached) {
return JSON.parse(cached);
}

// Fetch from DataForSEO
const result = await fetchFromDataForSEO(...);

// Cache result with TTL
await env.DFS_CACHE.put(cacheKey, JSON.stringify(result), {
expirationTtl: 86400 * 7, // 7 days
});

return result;

Category Taxonomy Caching​

DataForSEO categories are stored in KV with local JSON fallback:

import { getCategoryPath, getCategoryPaths } from "./dataforseo-categories.js";

// Single category lookup (uses in-memory cache -> KV -> local JSON)
const path = await getCategoryPath("12045", env);
// Returns: "Internet & Telecom > Web Services > SEO & SEM"

// Batch lookup
const paths = await getCategoryPaths(["12045", "12046"], env);
// Returns: { "12045": "...", "12046": "..." }

Location and Language Codes​

Common Location Codes​

CodeLocation
2840United States
2826United Kingdom
2124Canada
2036Australia
2276Germany
2250France
2392Japan
2076Brazil

Common Language Codes​

CodeLanguage
enEnglish
esSpanish
frFrench
deGerman
ptPortuguese
jaJapanese
zhChinese

Full lists: Location Codes | Language Codes


Database Integration​

Upsert Patterns​

RankFabric uses batched upserts to efficiently store DataForSEO data:

// Batched upsert for referring domains (50 per batch)
export async function upsertReferringDomains(items, targetDomain, env, options = {}) {
const statements = items.map((item) => {
const mapped = mapReferringDomain(item, targetDomain, options);
return env.DB.prepare(`
INSERT INTO referring_domains (...) VALUES (...)
ON CONFLICT(target_domain, referring_domain) DO UPDATE SET
rank = excluded.rank,
backlinks_count = excluded.backlinks_count,
...
`).bind(...);
});

// Execute in batches of 50 (D1 limit)
const BATCH_SIZE = 50;
for (let i = 0; i < statements.length; i += BATCH_SIZE) {
await env.DB.batch(statements.slice(i, i + BATCH_SIZE));
}
}

Weekly Snapshots​

Domain summaries are stored with year_week for historical tracking:

export function getCurrentYearWeek() {
const now = new Date();
const year = now.getFullYear();
const oneJan = new Date(year, 0, 1);
const week = Math.ceil(((now - oneJan) / 86400000 + oneJan.getDay() + 1) / 7);
return year * 100 + week; // e.g., 202547
}

See Also​