← Files 8gnc — Brand Growth DiagnosticARCHIVED FILE
skills/dataforseo/scripts/dataforseo_client.py
10.6 KB · Sep 30, 2026 · 23:15 UTC
"""Minimal DataForSEO API client.
Covers the convenience methods documented in SKILL.md plus generic post/get
escape hatches for endpoints not wrapped here. Uses only the standard library
+ requests so it works in any Python 3.9+ environment without a dependency
manager.
Usage:
from dataforseo_client import DataForSEOClient
client = DataForSEOClient(login, password)
result = client.serp_google_organic_live("brand strategy consultant",
location_name="United States",
language_name="English",
depth=100)
Credentials priority — author code wraps these env reads upstream:
1. DATAFORSEO_LOGIN / DATAFORSEO_PASSWORD (environment variables)
2. ~/.config/8gnc/dataforseo.json (only when the calling code loads it with user permission)
All endpoint paths are POST unless explicitly mapped to GET in the method.
"""
from __future__ import annotations
import base64
import json
from typing import Any
import requests
class DataForSEOClient:
"""Thin authenticated wrapper around the DataForSEO REST API."""
BASE_URL = "https://api.dataforseo.com/v3"
DEFAULT_TIMEOUT = 60
def __init__(self, login: str, password: str, timeout: int = DEFAULT_TIMEOUT) -> None:
if not login or not password:
raise ValueError(
"DataForSEO credentials missing. Set DATAFORSEO_LOGIN and "
"DATAFORSEO_PASSWORD environment variables."
)
creds = f"{login}:{password}".encode("utf-8")
self._auth_header = "Basic " + base64.b64encode(creds).decode("ascii")
self._timeout = timeout
def _headers(self) -> dict[str, str]:
return {"Authorization": self._auth_header, "Content-Type": "application/json"}
def post(self, path: str, task: list[dict[str, Any]] | dict[str, Any] | None = None) -> dict[str, Any]:
"""POST to any DataForSEO endpoint. `task` accepts the API's list-or-dict body."""
url = f"{self.BASE_URL}{path}" if path.startswith("/") else f"{self.BASE_URL}/{path}"
body = task if task is not None else []
resp = requests.post(url, headers=self._headers(), data=json.dumps(body), timeout=self._timeout)
resp.raise_for_status()
return resp.json()
def get(self, path: str) -> dict[str, Any]:
"""GET any DataForSEO endpoint (locations, languages, task_get/<id>, etc.)."""
url = f"{self.BASE_URL}{path}" if path.startswith("/") else f"{self.BASE_URL}/{path}"
resp = requests.get(url, headers=self._headers(), timeout=self._timeout)
resp.raise_for_status()
return resp.json()
def task_post(self, api_path: str, task: list[dict[str, Any]] | dict[str, Any] | None = None) -> dict[str, Any]:
"""Submit an async task (endpoints ending in /task_post)."""
return self.post(api_path, task)
def task_get(self, api_path: str, task_id: str) -> dict[str, Any]:
"""Retrieve results for an async task by id."""
suffix = api_path.rstrip("/")
return self.get(f"{suffix}/{task_id}")
def tasks_ready(self, api_path: str) -> dict[str, Any]:
"""List which async tasks are completed and ready to fetch."""
return self.get(api_path)
# ---- Convenience methods (high-traffic SERP / keyword / backlink endpoints) ----
def serp_google_organic_live(
self,
keyword: str,
location_name: str = "United States",
language_name: str = "English",
device: str = "desktop",
depth: int = 100,
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/serp/google/organic/live/advanced",
[{
"keyword": keyword,
"location_name": location_name,
"language_name": language_name,
"device": device,
"depth": depth,
**extra,
}],
)
def serp_google_maps_live(
self,
keyword: str,
location_name: str = "United States",
language_name: str = "English",
depth: int = 100,
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/serp/google/maps/live/advanced",
[{
"keyword": keyword,
"location_name": location_name,
"language_name": language_name,
"depth": depth,
**extra,
}],
)
def keywords_search_volume(
self,
keywords: list[str],
location_name: str = "United States",
language_name: str = "English",
**extra: Any,
) -> dict[str, Any]:
if len(keywords) > 700:
raise ValueError("keywords_search_volume accepts up to 700 keywords per call")
return self.post(
"/keywords_data/google_ads/search_volume/live",
[{
"keywords": keywords,
"location_name": location_name,
"language_name": language_name,
**extra,
}],
)
def keywords_for_site(
self,
target: str,
location_name: str = "United States",
language_name: str = "English",
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/keywords_data/google_ads/keywords_for_site/live",
[{
"target": target,
"location_name": location_name,
"language_name": language_name,
**extra,
}],
)
# ── AI Optimization API (LLM Mentions) ─────────────────────────────
# Who the AI engines cite. platform: "google" (AI Overviews) or
# "chat_gpt" (US-only per DataForSEO). targets: list of dicts like
# {"domain": "example.com", "search_scope": ["sources"]} or {"keyword": "..."}.
# Pricing (2026-06 official sheet): llm_mentions/* $0.10/task + $0.001/row;
# ai_keyword_data $0.01/task; llm_responses $0.0006/task.
# ⚠ llm_mentions/* requires DataForSEO's $100/mo minimum top-up through
# 2026-06-30 only — requirement removed July 1, 2026 (then plain pay-per-call).
def ai_llm_mentions_aggregated(
self,
targets: list[dict[str, Any]],
platform: str = "google",
location_name: str = "United States",
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/ai_optimization/llm_mentions/aggregated_metrics/live",
[{"platform": platform, "target": targets, "location_name": location_name, **extra}],
)
def ai_llm_mentions_search(
self,
targets: list[dict[str, Any]],
platform: str = "google",
location_name: str = "United States",
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/ai_optimization/llm_mentions/search/live",
[{"platform": platform, "target": targets, "location_name": location_name, **extra}],
)
def ai_llm_mentions_cross_aggregated(
self,
target_groups: list[dict[str, Any]],
platform: str = "google",
**extra: Any,
) -> dict[str, Any]:
"""Side-by-side compare. target_groups: 2-10 dicts like
{"aggregation_key": "us", "target": [{"domain": "ourbrand.com"}]}."""
return self.post(
"/ai_optimization/llm_mentions/cross_aggregated_metrics/live",
[{"platform": platform, "targets": target_groups, **extra}],
)
def ai_llm_mentions_top_domains(
self,
targets: list[dict[str, Any]],
platform: str = "google",
location_name: str = "United States",
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/ai_optimization/llm_mentions/top_domains/live",
[{"platform": platform, "target": targets, "location_name": location_name, **extra}],
)
def ai_llm_mentions_top_pages(
self,
targets: list[dict[str, Any]],
platform: str = "google",
location_name: str = "United States",
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/ai_optimization/llm_mentions/top_pages/live",
[{"platform": platform, "target": targets, "location_name": location_name, **extra}],
)
def ai_keywords_search_volume(
self,
keywords: list[str],
location_name: str = "United States",
**extra: Any,
) -> dict[str, Any]:
"""AI search volume per keyword — the cheap wide pass ($0.01/task)."""
return self.post(
"/ai_optimization/ai_keyword_data/keywords_search_volume/live",
[{"keywords": keywords, "location_name": location_name, **extra}],
)
def ai_llm_models(self) -> dict[str, Any]:
"""Models available to llm_responses."""
return self.get("/ai_optimization/llm_responses/models")
def ai_llm_response(self, payload: dict[str, Any]) -> dict[str, Any]:
"""Query an LLM directly through DataForSEO ($0.0006/task). Payload is
passed through unchanged — check llm_responses docs for current fields
and ai_llm_models() for model names."""
return self.post("/ai_optimization/llm_responses/live", [payload])
def backlinks_summary(self, target: str, **extra: Any) -> dict[str, Any]:
return self.post(
"/backlinks/summary/live",
[{"target": target, **extra}],
)
def onpage_task_post(self, target: str, max_crawl_pages: int = 100, **extra: Any) -> dict[str, Any]:
return self.post(
"/on_page/task_post",
[{"target": target, "max_crawl_pages": max_crawl_pages, **extra}],
)
def onpage_summary(self, task_id: str) -> dict[str, Any]:
return self.get(f"/on_page/summary/{task_id}")
def business_data_google_reviews(
self,
keyword: str,
location_name: str = "United States",
language_name: str = "English",
depth: int = 100,
**extra: Any,
) -> dict[str, Any]:
return self.post(
"/business_data/google/reviews/live/advanced",
[{
"keyword": keyword,
"location_name": location_name,
"language_name": language_name,
"depth": depth,
**extra,
}],
)
def serp_google_locations(self) -> dict[str, Any]:
return self.get("/serp/google/locations")
def serp_google_languages(self) -> dict[str, Any]:
return self.get("/serp/google/languages")
__all__ = ["DataForSEOClient"]
SHA-256: a1af238e87892b97f83421ea8f52e17fcf58f12f833e0db0462097028c297bc9