31 of 140 gap keywords already have a page of yours
rival.com and ledgerly.io both rank top 10 for 140 keywords you don't. 31 of them already show your pages in Search Console at position 20+, so you have a page to improve, not one to write.
Monthly: keywords where two competitors both rank in the top 10 and you are missing, checked against Search Console to show which already have a page.
Reads
You get
The card on the right is a sample. Yours shows your own numbers and updates in place every run.
What lands on your board
rival.com and ledgerly.io both rank top 10 for 140 keywords you don't. 31 of them already show your pages in Search Console at position 20+, so you have a page to improve, not one to write.
A keyword gap report from any SEO suite ends as a long list of things to write. Half of it is wrong: Google already shows one of your pages for many of those keywords, just on page three, where the suite does not count it. Writing a new page there splits your signals; improving the existing one is faster.
Once a month the agent asks DataForSEO Labs which keywords two of your competitors both rank for in the top 10, removes the ones you already rank for, and then looks each remaining keyword up in your own Search Console data. Content and SEO leads get two lists instead of one: keywords where a page of yours already shows and needs work, and keywords where you have nothing yet.
Call POST /v3/dataforseo_labs/google/domain_intersection/live with target1 and target2 set to the two competitor domains (no https:// or www.), intersections: true, item_types: ["organic"], your location_code and language_code, limit: 1000, and filters: [["first_domain_serp_element.rank_group", "<=", 10], "and", ["second_domain_serp_element.rank_group", "<=", 10]]. Keep keyword_data.keyword, keyword_data.keyword_info.search_volume and each side's rank_group and url.
Call POST /v3/dataforseo_labs/google/ranked_keywords/live with target set to your domain, the same location and language, item_types: ["organic"], filters: [["ranked_serp_element.serp_item.rank_group", "<=", 20]] and limit: 1000. If total_count is above 1,000, page with offset, at most 3 pages.
Normalize keywords on both lists (lowercase, trim, collapse spaces) and keep the competitors' keywords that are not in your ranked list: that anti-join is the gap. Drop keywords with a search volume under 50.
Call searchanalytics.query for the last 3 months with dimensions: ["query", "page"], rowLimit: 25000 and the country that matches your location. Join on the normalized query: a match with average position above 20 is a page to improve (note the page with most impressions); a match at 20 or better means Labs missed you, so drop it and count it; no match means a page to write.
Sort each list by search volume. Show the counts (gap, to improve, to write, dropped), the top keywords of each list with both competitors' positions, and the page of yours that should take the first one.
With BRIEFKIN_TOKEN set, the agent sends the result as one card that updates in place every run. Without it, you get the report in the chat.
Pick your agent, where the data comes from and how to schedule it; the commands below follow.
One command. It puts the SKILL.md in ~/.claude/skills and in ~/.agents/skills, which Codex, Cursor, Gemini CLI, Copilot CLI and OpenCode read too.
shellcurl -fsSL https://briefkin.dev/skills/dataforseo-keyword-gap/install | shAsk Claude Code:
promptRun the dataforseo-keyword-gap skillOr paste the prompt instead of installing:
promptUsing the DataForSEO Labs API (United States, English): call domain_intersection with target1 rival.com, target2 ledgerly.io, intersections true, organic only, limit 1000, filtered to keywords where both rank 10 or better. Then call ranked_keywords for my domain, organic, rank 20 or better, limit 1000. Lowercase and trim both lists and keep the competitors' keywords that are not on mine, with search volume 50+. For each, look the keyword up in my Google Search Console data for the last 3 months (query and page, United States). Split them into: shown in Search Console past position 20 (a page to improve, name the page), shown at 20 or better (Labs missed me, drop and count), and not shown at all (a page to write). Sort by search volume, give me the counts and the top of each list with both competitors' positions, and the page to improve first.1st of the month at 10:00, from cron on any machine where Claude Code is signed in:
crontab0 10 1 * * claude -p "Run the dataforseo-keyword-gap skill"Add it with crontab -e. A laptop asleep at that time runs it when it wakes if you use launchd or Task Scheduler instead; GitHub Actions works for the API route.
Connect Briefkin once with curl -fsSL https://briefkin.dev/install | sh and every run lands on your board as a report card. How setup works
SKILL.md---
name: dataforseo-keyword-gap
description: "Monthly: keywords where two competitors both rank in the top 10 and you are missing, checked against Search Console to show which already have a page. Use when the user asks for this, or when it runs on a schedule."
---
# Keyword gap vs competitors: DataForSEO Labs and Search Console
Monthly: keywords where two competitors both rank in the top 10 and you are missing, checked against Search Console to show which already have a page.
A keyword gap report from any SEO suite ends as a long list of things to write. Half of it is wrong: Google already shows one of your pages for many of those keywords, just on page three, where the suite does not count it. Writing a new page there splits your signals; improving the existing one is faster.
Once a month the agent asks DataForSEO Labs which keywords two of your competitors both rank for in the top 10, removes the ones you already rank for, and then looks each remaining keyword up in your own Search Console data. Content and SEO leads get two lists instead of one: keywords where a page of yours already shows and needs work, and keywords where you have nothing yet.
## Two ways to get the data
Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.
## Route A: API or MCP server
### What you need
- DataForSEO API login and password (or the DataForSEO MCP server). A run makes 2 to 4 DataForSEO Labs requests, roughly a cent each plus a fraction of a cent per row returned; try the requests on `https://sandbox.dataforseo.com/v3/` first.
- Read access to the Search Console property, and two competitor domains that really compete for your search traffic, not just your product.
If something is missing, say what and stop; do not invent numbers.
### Steps
1. **Find what both competitors rank for** (DataForSEO): Call `POST /v3/dataforseo_labs/google/domain_intersection/live` with `target1` and `target2` set to the two competitor domains (no `https://` or `www.`), `intersections: true`, `item_types: ["organic"]`, your `location_code` and `language_code`, `limit: 1000`, and `filters: [["first_domain_serp_element.rank_group", "<=", 10], "and", ["second_domain_serp_element.rank_group", "<=", 10]]`. Keep `keyword_data.keyword`, `keyword_data.keyword_info.search_volume` and each side's `rank_group` and `url`.
2. **Get what you already rank for** (DataForSEO): Call `POST /v3/dataforseo_labs/google/ranked_keywords/live` with `target` set to your domain, the same location and language, `item_types: ["organic"]`, `filters: [["ranked_serp_element.serp_item.rank_group", "<=", 20]]` and `limit: 1000`. If `total_count` is above 1,000, page with `offset`, at most 3 pages.
3. **Take out what you already have**: Normalize keywords on both lists (lowercase, trim, collapse spaces) and keep the competitors' keywords that are not in your ranked list: that anti-join is the gap. Drop keywords with a search volume under 50.
4. **Check each gap keyword in Search Console** (Google Search Console): Call `searchanalytics.query` for the last 3 months with `dimensions: ["query", "page"]`, `rowLimit: 25000` and the country that matches your location. Join on the normalized query: a match with average position above 20 is a page to improve (note the page with most impressions); a match at 20 or better means Labs missed you, so drop it and count it; no match means a page to write.
5. **Post both lists**: Sort each list by search volume. Show the counts (gap, to improve, to write, dropped), the top keywords of each list with both competitors' positions, and the page of yours that should take the first one.
## Route B: the browser
### What you need
- Signed in to the DataForSEO dashboard with a funded balance (Playground requests are charged like API calls), and to Google with access to the Search Console property.
If something is missing, say what and stop; do not invent numbers.
### Steps
Start at https://app.dataforseo.com/. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.
1. **Run the domain intersection**: In the DataForSEO dashboard open API Playground, choose DataForSEO Labs > Google > Domain Intersection, enter the two competitor domains as target 1 and target 2, set your location and language, leave intersections on, set the limit to `1000` and click Request. Keep keywords where both `rank_group` values are 10 or better, with search volume.
2. **Run your ranked keywords**: In the same Playground choose Ranked Keywords, enter your domain with the same location and language, limit `1000`, and click Request. Remove every keyword you rank 20 or better for from the first list; what is left is the gap.
3. **Look each gap keyword up in Search Console**: Open `https://search.google.com/search-console/performance/search-analytics`, set Date to `Last 3 months` and your country, turn on Average position, and export the Queries table with Export > Download CSV. Match the gap keywords against the export in lowercase.
4. **Sort into improve, write and drop**: Past position 20 in Search Console: a page to improve (click the query, then the Pages tab, to see which page). At 20 or better: drop it. Not in the export: a page to write. Sort both lists by search volume.
Note: Each Playground request is billed like the API, so run each one once and save the JSON. Search Console exports cap at 1,000 rows; gap keywords past position 20 are often beyond that on large sites, where the API route is the reliable one.
## The report
- **The gap**: how many keywords both competitors rank top 10 for while you are not in the top 20.
- **Pages to improve**: gap keywords where Search Console already shows your page past position 20, with the page.
- **Pages to write**: gap keywords with no impressions at all.
- **Dropped**: keywords Labs missed that you do rank for.
- The page to improve first.
## Post it to Briefkin
When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.
```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "dataforseo-keyword-gap/<date of this run, e.g. 2026-10-16>",
"item_key": "dataforseo-keyword-gap",
"project": "example.com",
"source": "Keyword gap vs competitors: DataForSEO Labs and Search Console skill",
"kind": "update",
"title": "31 of 140 gap keywords already have a page of yours",
"summary": "**rival.com** and **ledgerly.io** both rank top 10 for **140 keywords** you don't. **31** of them already show your pages in Search Console at position 20+, so you have a page to improve, not one to write.",
"widget": [
{
"type": "Badges",
"props": {
"items": [
{
"label": "140 in the gap",
"tone": "info"
},
{
"label": "31 to improve",
"tone": "ok"
},
{
"label": "109 to write",
"tone": "warn"
},
{
"label": "6 dropped: you rank",
"tone": "neutral"
}
]
}
},
{
"type": "Table",
"props": {
"columns": [
"Keyword",
"Searches/mo",
"rival.com",
"ledgerly.io",
"Your page (GSC)",
"Your pos"
],
"align": [
"left",
"right",
"right",
"right",
"left",
"right"
],
"rows": [
[
"invoice factoring",
6600,
4,
7,
"/blog/cash-flow-tips",
34.2
],
[
"net 30 payment terms",
4400,
2,
5,
"/blog/payment-terms",
27.8
],
[
"proforma invoice",
3600,
6,
3,
"/templates/proforma",
22.5
],
[
"late payment fee",
2900,
8,
9,
"none",
"to write"
],
[
"invoice vs receipt",
2400,
3,
4,
"none",
"to write"
],
[
"credit note template",
1900,
5,
10,
"/templates",
41
]
]
}
},
{
"type": "Callout",
"props": {
"text": "Start with /blog/payment-terms: it already shows for \"net 30 payment terms\" (4,400 searches) at 27.8, and both rivals answer it with a dedicated page.",
"tone": "info"
}
}
]
}
JSON
```
Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.
## Run it on a schedule
Suggested: 1st of the month at 10:00 (`0 10 1 * *`). From cron, with the agent signed in on that machine:
```bash
0 10 1 * * claude -p "Run the dataforseo-keyword-gap skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```
Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/dataforseo-keyword-gap#schedule
Source: https://briefkin.dev/skills/dataforseo-keyword-gap
DataForSEO API login and password (or the DataForSEO MCP server). A run makes 2 to 4 DataForSEO Labs requests, roughly a cent each plus a fraction of a cent per row returned; try the requests on https://sandbox.dataforseo.com/v3/ first.
Read access to the Search Console property, and two competitor domains that really compete for your search traffic, not just your product.
npx dataforseo-mcp-server and set DATAFORSEO_USERNAME and DATAFORSEO_PASSWORD to your API login and API password (Dashboard > API Access); add http to the command for clients that want a local HTTP server. A hosted server runs at https://mcp.dataforseo.com/mcp for clients that can send a Basic authorization header. You can turn off the modules a skill doesn't use to keep the tool list short (setup guide).DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD and call the endpoints with curl -u "$DATAFORSEO_LOGIN:$DATAFORSEO_PASSWORD". The free sandbox at https://sandbox.dataforseo.com/v3/ returns sample data at no cost, which is the place to test a skill before it spends money.The API password can spend your balance but cannot sign in to the dashboard or change billing. Set a low balance alert in the dashboard, and give each skill a fixed keyword list and depth so a run costs the same every time.
GOOGLE_APPLICATION_CREDENTIALS at the key and set GSC_SITE_URL to sc-domain:example.com or https://www.example.com/.webmasters.readonly scope and a stored refresh token works for scheduled runs.Ask for webmasters.readonly and add the service account as a Restricted user; the reports only read.
DataForSEO Labs checks each keyword's results on its own update cycle, from one location, while Search Console averages every impression over the period across all your pages. A page that ranks on some days, or only in some cities, shows in Search Console and can be missing from Labs. The skill treats Search Console as the truth for your own site and drops those keywords from the gap.
Two to four Labs requests: one domain intersection and one to three pages of your ranked keywords. At list prices that is well under a dollar a month. For three competitors, run the intersection for each pair (three requests) and merge the results; keep the location and language fixed so the cost stays the same every month.
No. The skill runs with your own agent and gives you the report in the chat. With a Briefkin token set, each run also posts it to your board as a report card, so you can glance at it instead of asking again.
Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot CLI, OpenCode, Windsurf and Amp all read SKILL.md files. The install command puts it in ~/.claude/skills and ~/.agents/skills, where they look. ChatGPT, the Claude app and Grok take the prompt instead. Pick your agent under "Install and run it" and the commands change to match.
Yes. Switch the data source to "Your browser" and the steps change to the DataForSEO and Google Search Console pages themselves: the agent opens app.dataforseo.com in a browser you are signed in to and reads the numbers off the page. Claude Code, Codex, Cursor, Claude app and ChatGPT can drive a browser out of the box; Gemini CLI, Copilot CLI and OpenCode need a Playwright or Chrome DevTools MCP server first. It is slower than the API and needs you signed in, so it suits a report you run by hand or from a scheduler on your own computer.
Skills are free to use with any agent. Briefkin is where their results land: one board for every report, number and question your agents send, with a ping when something needs you.
Free during the open beta. No card.