{"id":19533,"plugin_id":"plugins_6a99cda269b0819180d65893e1de7811","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:15:38.182Z","digest":"adc126466eaf87a8f15d9e362394071fd22963690e65351a107467f60c22947a","against":null,"payload":{"name":"dataforseo","description":"Use when the user asks to perform SEO research, keyword analysis, SERP audits, backlink checks, competitor analysis, or any task involving the DataForSEO API. Provides the complete Python client, authentication, response parsing patterns, and all available endpoints. Also use when writing scripts that call DataForSEO or when the user mentions keyword research, search volume, SERP rankings, or SEO data collection.","included_files":[{"relative_path":"scripts/dataforseo_client.py","size_in_bytes":10804}],"skill_md_contents":"---\nname: dataforseo\ndescription: >-\n  Use when the user asks to perform SEO research, keyword analysis, SERP audits,\n  backlink checks, competitor analysis, or any task involving the DataForSEO API.\n  Provides the complete Python client, authentication, response parsing patterns,\n  and all available endpoints. Also use when writing scripts that call DataForSEO\n  or when the user mentions keyword research, search volume, SERP rankings, or\n  SEO data collection.\n---\n\n# DataForSEO API — Operational Guide\n\n## File locations (read this first)\n\nChoose behavior based on the current execution surface:\n\n| Context | Client access | Working dir | User-facing output |\n|---|---|---|---|\n| Codex with local shell access | Resolve this installed skill directory and use `scripts/dataforseo_client.py` | current workspace | `./dataforseo-results/YYYYMMDD/` or another user-approved path |\n| ChatGPT or a surface without local shell access | No direct API execution in this skills-only release | conversation or supported file workspace | Return an input contract or analyze user-supplied exports; never fabricate live API results |\n\nThis release has no authenticated DataForSEO MCP connection.\n\n## When NOT to Use\n\n- No DataForSEO account or API budget. Every call bills the account — there's no free tier worth building on.\n- You only need your own site's organic data. Google Search Console gives you queries, clicks, impressions, and positions for free.\n- One-off single-keyword lookups. Scripting a client and paying API spend for one number isn't worth it — use a free volume tool instead.\n\n## Credentials\n\nUse one of these local credential sources, in priority order:\n\n1. **Environment variables:** `DATAFORSEO_LOGIN` and `DATAFORSEO_PASSWORD`.\n2. **Local config file:** `~/.config/8gnc/dataforseo.json` with `{\"login\": \"...\", \"password\": \"...\"}`. Create or read it only with the user's permission and never print its contents.\n\nDo not paste credentials into chat, source files, reports, or committed project configuration.\n\n## Bundled client\n\nA working `DataForSEOClient` ships inside the skill at `scripts/dataforseo_client.py` (~200 LoC, requests-based, covers every convenience method in the table below + generic `post`/`get`/`task_post`/`task_get`/`tasks_ready` escape hatches).\n\n**To use it:** on a local execution surface, resolve the installed `dataforseo` skill directory from this loaded `SKILL.md` and add its `scripts/` directory to the Python import path. Do not copy the client into the user's project unless the user explicitly asks for a vendored copy.\n\nThe client only depends on `requests`. Standard library otherwise. Python 3.9+.\n\n## Quick Start (Copy-Paste Pattern)\n\nEvery local DataForSEO script should start with this pattern after resolving the bundled client path:\n\n```python\nimport json, sys, os, time\nfrom pathlib import Path\n\nfrom dataforseo_client import DataForSEOClient\n\nAPI_LOGIN = os.environ.get(\"DATAFORSEO_LOGIN\")\nAPI_PASSWORD = os.environ.get(\"DATAFORSEO_PASSWORD\")\n\nclient = DataForSEOClient(API_LOGIN, API_PASSWORD)\nOUTPUT_DIR = Path.cwd() / \"dataforseo-results\" / time.strftime(\"%Y%m%d\")\nOUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n```\n\n- Add `time.sleep(2)` between API calls to respect rate limits.\n- Save raw responses as JSON for re-analysis without re-billing the API.\n- On a non-local surface, stop before the API call and request an export or an authenticated MCP capability.\n\n## Available Client Methods\n\n### Convenience Methods (Use These First)\n\n| Method | What It Does | Endpoint |\n|--------|-------------|----------|\n| `client.serp_google_organic_live(keyword, location_name, language_name, device, depth)` | Live Google organic SERP results | `/serp/google/organic/live/advanced` |\n| `client.serp_google_maps_live(keyword, location_name, language_name, depth)` | Live Google Maps SERP results | `/serp/google/maps/live/advanced` |\n| `client.keywords_search_volume(keywords_list, location_name, language_name)` | Search volume for a list of keywords (up to 700 per call) | `/keywords_data/google_ads/search_volume/live` |\n| `client.keywords_for_site(target_domain, location_name, language_name)` | Keywords Google associates with a domain | `/keywords_data/google_ads/keywords_for_site/live` |\n| `client.backlinks_summary(target_url_or_domain)` | Backlink profile summary | `/backlinks/summary/live` |\n| `client.onpage_task_post(target_url)` | Submit async on-page SEO audit | `/on_page/task_post` |\n| `client.onpage_summary(task_id)` | Get on-page audit results | `/on_page/summary/{task_id}` |\n| `client.business_data_google_reviews(keyword, location_name, language_name, depth)` | Google Business reviews | `/business_data/google/reviews/live/advanced` |\n| `client.serp_google_locations()` | List all available SERP locations | `/serp/google/locations` |\n| `client.serp_google_languages()` | List all available SERP languages | `/serp/google/languages` |\n\n### Generic Methods (For Any Endpoint)\n\n| Method | When to Use |\n|--------|-------------|\n| `client.post(endpoint_path, [task_dict])` | Any POST endpoint not covered by convenience methods |\n| `client.get(endpoint_path)` | Any GET endpoint |\n| `client.task_post(api_path, [task_dict])` | Submit async tasks (endpoints ending in `/task_post`) |\n| `client.task_get(api_path, task_id)` | Retrieve async task results |\n| `client.tasks_ready(api_path)` | Check which async tasks are completed |\n\nAll convenience methods accept `**extra` kwargs — you can pass any additional DataForSEO parameter without modifying the client.\n\n## Response Structure (Critical)\n\n**Every DataForSEO response has this structure:**\n\n```python\n{\n    \"version\": \"0.1.20260209\",\n    \"status_code\": 20000,        # 20000 = success\n    \"status_message\": \"Ok.\",\n    \"tasks\": [\n        {\n            \"id\": \"task-uuid\",\n            \"status_code\": 20000,\n            \"status_message\": \"Ok.\",\n            \"result\": [...]      # <-- THE DATA IS HERE\n        }\n    ]\n}\n```\n\n**Standard parsing pattern:**\n\n```python\nresult = client.some_method(...)\nif result.get(\"tasks\"):\n    for task in result[\"tasks\"]:\n        if task.get(\"result\"):\n            for item in task[\"result\"]:\n                # Process item here\n```\n\n### Search Volume Result Items\n\nEach item in a search volume result contains:\n- `item[\"keyword\"]` — the keyword string\n- `item[\"search_volume\"]` — monthly search volume (int or None)\n- `item[\"competition\"]` — \"LOW\", \"MEDIUM\", \"HIGH\", or None\n- `item[\"cpc\"]` — cost per click (float or None)\n- `item[\"monthly_searches\"]` — list of dicts with `year`, `month`, `search_volume`\n\n### SERP Result Items\n\nEach SERP result contains an `items` list. Items have a `type` field:\n- `type == \"organic\"` — organic search result: has `title`, `url`, `rank_absolute`, `description`\n- `type == \"people_also_ask\"` — PAA box: has `items` list of dicts with `title` key\n- `type == \"related_searches\"` — related searches: has `items` list of **plain strings, NOT dicts** — check `isinstance(item, str)` when iterating, or your dict-style parsing will crash here\n- `type == \"featured_snippet\"` — featured snippet: has `title`, `url`, `description`\n- `type == \"local_pack\"` — local pack: has `items` list with business details\n- `type == \"paid\"` — paid ads: has `title`, `url`\n\n**IMPORTANT: `related_searches` items are plain strings, NOT dicts. Check `isinstance(item, str)` when iterating.**\n\n### Keywords-for-Site Result Items\n\nEach item contains:\n- `item[\"keyword\"]` — keyword string\n- `item[\"search_volume\"]` — monthly volume\n- `item[\"competition\"]` — LOW/MEDIUM/HIGH\n\n## Common Endpoint Patterns (via client.post)\n\n### Keyword Suggestions from Seed Keywords\n\n```python\nresult = client.post(\"/keywords_data/google_ads/keywords_for_keywords/live\", [{\n    \"keywords\": [\"adaptive reuse\", \"historic preservation\"],\n    \"location_name\": \"United States\",\n    \"language_name\": \"English\",\n}])\n```\n\n### Backlinks (More Endpoints)\n\n```python\n# Backlinks list\nresult = client.post(\"/backlinks/backlinks/live\", [{\"target\": \"example.com\", \"limit\": 100}])\n\n# Referring domains\nresult = client.post(\"/backlinks/referring_domains/live\", [{\"target\": \"example.com\", \"limit\": 100}])\n\n# Anchors\nresult = client.post(\"/backlinks/anchors/live\", [{\"target\": \"example.com\", \"limit\": 100}])\n```\n\n### Domain Analytics\n\n```python\n# Technologies used by a domain\nresult = client.post(\"/domain_analytics/technologies/domains_by_technology/live\", [{\n    \"technology\": \"WordPress\",\n    \"filters\": [\"country_iso_code\", \"=\", \"US\"],\n}])\n```\n\n### Google Trends\n\n```python\nresult = client.post(\"/keywords_data/google_trends/explore/live\", [{\n    \"keywords\": [\"adaptive reuse\", \"office conversion\"],\n    \"location_name\": \"United States\",\n    \"language_name\": \"English\",\n    \"time_range\": \"past_12_months\",\n}])\n```\n\n## Location Names (Common Values)\n\nUse these exact strings for `location_name`:\n- `\"United States\"`\n- `\"New York,New York,United States\"` (city-level)\n- `\"Dallas,Texas,United States\"` (city-level)\n- `\"Houston,Texas,United States\"` (city-level)\n- `\"Austin,Texas,United States\"` (city-level)\n- `\"United Kingdom\"`, `\"Canada\"`, `\"Australia\"`, etc.\n\nFor exact location IDs, call `client.serp_google_locations()`.\n\n## Best Practices\n\n1. **Always save raw JSON** — save every API response to the results dir (see File locations) so data can be re-analyzed without re-calling the API.\n2. **Batch keywords** — `keywords_search_volume` accepts up to 700 keywords per call. Chunk larger lists.\n3. **Rate limit** — add `time.sleep(2)` between calls. For SERP calls (heavier), use `time.sleep(3)`.\n4. **Handle None values** — `search_volume`, `cpc`, and `competition` can all be None. Always use `or 0` / `or \"-\"` in formatting.\n5. **Use depth=100 for SERP audits** — default depth=10 only returns page 1. Use depth=100 to get related_searches and people_also_ask which appear on later pages.\n6. **Sort by volume** — when displaying results, always sort keywords by search_volume descending for readability.\n7. **Deliverable delivery depends on surface** — use a user-approved workspace path in Codex; use the supported file workflow in ChatGPT. Never claim a live API result when no authenticated execution path exists.\n\n## Full Example: Keyword Research Workflow\n\nThis example uses local environment variables and a workspace-scoped output directory.\n\n```python\nimport json, sys, os, time\nfrom pathlib import Path\n\n# Resolve the installed dataforseo skill and add its scripts directory to sys.path first.\nfrom dataforseo_client import DataForSEOClient\n\nAPI_LOGIN = os.environ.get(\"DATAFORSEO_LOGIN\")\nAPI_PASSWORD = os.environ.get(\"DATAFORSEO_PASSWORD\")\n\nclient = DataForSEOClient(API_LOGIN, API_PASSWORD)\nOUTPUT_DIR = Path.cwd() / \"dataforseo-results\" / time.strftime(\"%Y%m%d\")\nOUTPUT_DIR.mkdir(parents=True, exist_ok=True)\n\ndef save(name, data):\n    path = OUTPUT_DIR / f\"{name}.json\"\n    with open(path, \"w\", encoding=\"utf-8\") as f:\n        json.dump(data, f, indent=2, ensure_ascii=False)\n\n# 1. Search volume for target keywords\nkeywords = [\"keyword one\", \"keyword two\", \"keyword three\"]\nvol_result = client.keywords_search_volume(keywords)\nsave(\"search_volume\", vol_result)\n\n# Parse results\nif vol_result.get(\"tasks\"):\n    for task in vol_result[\"tasks\"]:\n        if task.get(\"result\"):\n            items = sorted(task[\"result\"], key=lambda x: (x.get(\"search_volume\") or 0), reverse=True)\n            for item in items:\n                kw = item.get(\"keyword\", \"?\")\n                vol = item.get(\"search_volume\") or 0\n                comp = item.get(\"competition\", \"-\") or \"-\"\n                cpc = item.get(\"cpc\")\n                cpc_str = f\"${cpc:.2f}\" if cpc else \"-\"\n                print(f\"{kw:<50} vol={vol:<8} comp={comp:<10} cpc={cpc_str}\")\n\ntime.sleep(2)\n\n# 2. SERP audit for key terms\nserp_result = client.serp_google_organic_live(\"target keyword\", depth=100)\nsave(\"serp_audit\", serp_result)\n\n# Parse SERP — organics, PAA, and related searches\nif serp_result.get(\"tasks\"):\n    for task in serp_result[\"tasks\"]:\n        if task.get(\"result\"):\n            for res in task[\"result\"]:\n                for item in res.get(\"items\", []):\n                    t = item.get(\"type\", \"\")\n                    if t == \"organic\":\n                        print(f\"#{item.get('rank_absolute')} {item.get('title','')[:60]}\")\n                        print(f\"   {item.get('url','')[:70]}\")\n                    elif t == \"people_also_ask\":\n                        for pa in item.get(\"items\", []):\n                            if isinstance(pa, dict):\n                                print(f\"PAA: {pa.get('title','')}\")\n                    elif t == \"related_searches\":\n                        for rs in item.get(\"items\", []):\n                            if isinstance(rs, str):\n                                print(f\"Related: {rs}\")\n\ntime.sleep(2)\n\n# 3. Keyword suggestions for a competitor domain\nsite_result = client.keywords_for_site(\"competitor.com\")\nsave(\"competitor_keywords\", site_result)\n\n# 4. Keyword suggestions from seed keywords\nseed_result = client.post(\"/keywords_data/google_ads/keywords_for_keywords/live\", [{\n    \"keywords\": [\"seed keyword\"],\n    \"location_name\": \"United States\",\n    \"language_name\": \"English\",\n}])\nsave(\"seed_suggestions\", seed_result)\n```\n\n## Existing Research Data\n\nPrevious research results may be saved at the results path for your surface (see File locations table). Check for existing data before making redundant API calls.\n\n## API Reference\n\nFor the complete list of all available DataForSEO API endpoints, parameters, and response schemas, consult the [official DataForSEO API documentation](https://docs.dataforseo.com/v3/). The convenience methods in this skill cover the highest-traffic endpoints; the `client.post()` and `client.get()` generic methods reach anything else.\n\n> Earlier versions of this skill referenced a bundled `reference.md` that was not shipped — use the official DataForSEO docs instead.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}