This morning I ran a keyword gap between gelato.com and printify.com, mostly to see if anything had moved since last week. Printify ranks in Google's top 20 for 21,186 keywords (with at least 10 searches a month) that Gelato doesn't rank for at all. The single biggest one, at 368,000 searches a month, was "happy mother's day day". Yes, with the extra "day". Printify sits at #18 for it.
Right behind that, all at 165,000 a month: "how to make money online" (Printify #12), "wix create a website" (#9), "wix build a website" (#7) and "website creation wix" (#9). Printify has blog posts that rank for Wix tutorials. Good for them. It's a real gap, and it's also nothing Gelato should chase.
That's the honest problem with competitor keywords. The raw list is huge and mostly noise, and the useful part is buried. So I built Keyword Gap Finder to make the filtering part of the call instead of a spreadsheet chore afterwards.
(The Store links here carry my Apify affiliate ID, so Apify pays me a small commission if you sign up through one.)
What a run gives you
You pass your domain and up to 5 competitors. For each competitor it pulls the Google keywords they rank for (top 20 by default, you can go to 100) in the country you pick, drops the ones you already rank for, and merges what's left into one row per keyword. Each row has search volume, CPC, keyword difficulty, intent, how many of your competitors rank for it, each one's position and ranking URL, and which competitor is best placed. There's also one summary row per competitor with the total size of the gap, so you know how big the pile is even when you only ask for the top 20.
My second run today added Printful and "excludeKeywordsContaining": ["wix"]. The Wix filter only took 76 keywords off Printify's count (21,186 down to 21,110), but they were the loud ones. Printful's gap against Gelato is bigger, 30,429 keywords. The top of the merged list got a lot more useful: "black friday" and "when is black friday" (Printful at #9 and #5), "amazon on demand" (Printful #7), "dropshipping" (Printful #6, but keyword difficulty 74), "sublimation" (Printful #4, difficulty 25) and "embroidered" (165,000 a month, Printful #12, difficulty 1). That last one is the kind of thing I'd actually hand to a writer.
There are two other modes, and honestly "behind" is the one I'd start with. It returns keywords you both rank for where a competitor is ahead of you. Gelato is behind Printify on 8,666 shared keywords and behind Printful on 8,566. The top ones today were Etsy pages Gelato already has: "etsy store ideas" (Printify 1, Gelato 3), "etsy shop idea" (Printify 1, Gelato 3), "ideas for an etsy shop" (Printify 2, Gelato 3) and "etsy shop ideas" (Printify 2, Printful 3, Gelato 8). Then "niches" at 301,000 a month with Printify at 7 and Gelato at 17. Moving an existing page from 3 to 1 is a much smaller job than ranking a new one from nothing. The third mode, "shared", just returns every keyword you have in common.
The public preset on the Store, Keyword Gap: Keywords Your Competitors Outrank You On, is exactly that behind report: gelato.com against printify.com and printful.com, top 50. Swap in your own domains and run it.
The code
Python with apify-client 3.x. In 3.x call() returns a Run object, so the dataset ID is run.default_dataset_id, not run["defaultDatasetId"]:
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("vncntlabs/keyword-gap-finder").call(run_input={
"yourDomain": "gelato.com",
"competitors": ["printify.com", "printful.com"],
"maxKeywords": 20,
"excludeKeywordsContaining": ["wix"],
})
for row in client.dataset(run.default_dataset_id).iterate_items():
if row["type"] == "competitor":
print(row["competitor"], row["status"], "| gap size", row.get("totalKeywords"))
elif row["type"] == "keyword":
print(row["searchVolume"], row["keywordDifficulty"], row["keyword"], row["positions"])
That's the exact script behind the Printful numbers above. It took about 14 seconds end to end from my machine (6.7 seconds of that was the run itself), and the first lines looked like this:
printify.com ok | gap size 21110
printful.com ok | gap size 30429
368000 3 happy mother's day day {'printify.com': 18}
301000 80 black friday {'printful.com': 9}
301000 15 amazon on demand {'printful.com': 7}
201000 37 dropshipper {'printful.com': 16}
201000 53 when is black friday {'printful.com': 5}
201000 48 y2k {'printful.com': 11}
201000 74 dropshipping {'printful.com': 6}
The behind report with curl, as one synchronous call that returns the rows directly:
curl -sS -X POST "https://api.apify.com/v2/acts/vncntlabs~keyword-gap-finder/run-sync-get-dataset-items?timeout=290" \
-H "Authorization: Bearer $APIFY_TOKEN" -H "Content-Type: application/json" \
-d '{"yourDomain":"gelato.com","competitors":["printify.com","printful.com"],"mode":"behind","maxKeywords":20}'
That came back in about 12 seconds with 22 rows (2 competitor summaries and 20 keywords). Each keyword row has a yourPosition field next to the competitors' positions, which is all you need to sort by "closest to winning".
In Claude, Cursor or any other MCP client, add this as a remote MCP server. It loads only this one Actor, and you sign in to Apify in the browser, so no token goes in the file:
{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com/?tools=vncntlabs/keyword-gap-finder"
}
}
}
Then a prompt like "find keywords printful.com ranks for that gelato.com doesn't, skip anything with wix in it" does what you'd expect. The Node version is in the repo too.
Code: https://github.com/PravinrajV/vncntlabs-apify-examples/tree/main/11-keyword-gap-finder
Speed and cost
It's quick because nothing gets crawled. It's one data request per competitor. Today's one-competitor run took 3.7 seconds on Apify, the two-competitor run 6.7 and the two-competitor behind run 11.4.
Price is $0.005 per run, $0.02 per competitor compared and $0.001 per keyword returned, so the keywords themselves are $1 per 1,000. The fixed parts matter on small reports. One competitor and 10 keywords is $0.035. Two competitors and 20 keywords (both of today's bigger runs) is $0.065. The public preset, two competitors and 50 keywords, is $0.095. Pulling 1,000 keywords across two competitors would be $1.045. A competitor with no ranking data isn't charged. On the free Apify plan a run is capped at 2 competitors and 50 keywords.
How it compares
I checked these today (November 10, 2026) on the Apify Store API and on Semrush's and Ahrefs' own pricing pages. I haven't run the other Actors on the same domains, so this is prices and what each one says it does, not a quality test.
| Tool | What it does | Price per 1,000 keywords |
|---|---|---|
| vncntlabs/keyword-gap-finder | Missing, behind and shared modes across up to 5 competitors, merged into one row per keyword with every competitor's position and URL | $1 + $0.02 per competitor + $0.005 per run |
| amassuo/semrush-competitor-keywords | Every keyword one domain ranks for, with optional rival discovery and Excel export | $2 + $0.002 per competitor it finds |
| doesaiknow/doesaiknow-competitor-keywords-apify | A domain's ranked keywords plus a gap against your site, bulk domains | $4 on the free plan, $2 Bronze, $1.80 Silver, $1.70 Gold and up |
| santhej/ranked-keywords-checker | Every keyword a domain ranks for, 190+ countries, optional domain overview and competitors | $3 free and Bronze, $2.70 Silver, $2.40 Gold and up |
| Semrush Keyword Gap | Up to 5 domains side by side in the UI, organic, paid or Shopping keywords, with Missing, Weak, Untapped and more | Part of the SEO plans, from $139 a month billed monthly |
| Ahrefs Competitive Analysis | Content gap in the UI | Part of Lite, $129 a month billed monthly |
Where they're better, plainly. amassuo's Actor has over 1,000 users and mine has 1. If what you want is a competitor's full keyword inventory to slice yourself, amassuo, santhej and doesaiknow are all built for that, and doesaiknow does a gap too. Just remember that doing a gap yourself from raw ranked-keyword lists means paying for your own domain's list as well. Semrush and Ahrefs are better if you're in an SEO tool every day: you get history, a UI built for exactly this, and Semrush's Weak and Untapped buckets are nicer than anything I return. If you already pay for one of them, you don't need my Actor for this.
Where mine fits is when the gap itself is the output and it has to run somewhere other than a browser tab. A script, a scheduled job, an n8n flow, a client report, or an agent that calls it as a tool. A two-competitor report costs a few cents, and you don't need a monthly plan for something you run twice a month.
Where it falls short
The rankings come from DataForSEO Labs, not a live Google search. Positions can be a few weeks old and volumes are estimates, like in every keyword tool. Small sites with little traffic often come back with no data at all, and those aren't charged, but you also get nothing.
The branded filter only removes your brand and that competitor's own brand from each competitor's list. Today "printful" (135,000 a month) showed up as a gap keyword because Printify ranks #16 for it. To Gelato that's a third brand, so it stays. Add rival brand names to excludeKeywordsContaining yourself.
And it only knows Google organic results. No ads, no Bing, no AI answers, and no topic clustering. You get keywords, not a content plan.
Do you start with the missing keywords or the ones where you're already close? I keep ending up in the behind list.
Disclosure: I build the Apify Actor used here (VNCNT Labs). Every number in this post comes from a real run on Nov 10, 2026 (runs JAsnH9YlTP6kE6Lt7, 0pGFzepd5UWsbXYUx and REoZRJa434umt7VsC), and the prices are Apify Store, Semrush and Ahrefs list prices checked that day.
Top comments (0)