# Competitor Keywords & Keyword Gap — Semrush/Ahrefs Alternative (`memo23/competitor-keywords`) Actor

Find the Google keywords your competitors rank for and you don't: one row per keyword with every competitor's position side by side, an opportunity score and quick wins. Also lists any site's ranked keywords and its organic competitors. 88 countries, pay per row.

- **URL**: https://apify.com/memo23/competitor-keywords.md
- **Developed by:** [Muhamed Didovic](https://apify.com/memo23) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 gap keywords

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Competitor Keywords & Keyword Gap — Semrush/Ahrefs Alternative

<p align="center"><img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/competitor-keywords-logo.png" width="140" alt="Competitor Keywords"></p>

Find the Google keywords your competitors rank for and you don't. Compare your site with up to five competitors and get **one row per keyword with every competitor's position side by side**, an **opportunity score** from 0 to 100, and a **quick-win flag** for keywords where you sit on page two and a competitor holds the top three.

The same actor also lists **every keyword any site ranks for** and **finds your organic competitors**. 88 countries, no Semrush or Ahrefs subscription, and you pay per row.

***

### Why use this actor?

- **One row per keyword, not one per pair.** With three competitors you get one row for "check backlinks", showing `{ "semrush.com": 2, "ahrefs.com": 1 }` next to your own position. You don't have to join three exports yourself.
- **Sorted by what is worth doing.** Every gap row carries an `opportunityScore` built from search volume, keyword difficulty, CPC and how many competitors already rank. The best opportunities come first.
- **Quick wins called out.** Keywords where you rank 11-20 and a competitor ranks 1-3 are flagged with `isQuickWin`, and listed first if you want.
- **Brand terms left out.** "semrush login" is not a keyword moz.com can win. Keywords containing any compared site's name are dropped, and the log tells you how many.
- **No competitor list needed.** Leave it empty and the three sites that share the most keywords with yours are picked for you. YouTube, Reddit and other giants that share keywords with everyone are skipped.
- **Pay for rows you get.** Filters run before the data is fetched, and the row limit is also what we request, so rows you filtered out are never billed.

***

### Overview

This actor is for SEO teams, agencies, content strategists and founders who want competitor keyword data without a monthly SEO-suite seat. It reads Google organic ranking data from DataForSEO Labs for the country and language you choose.

It writes to one dataset with a `type` on every row:

| `type` | Produced by | One row per |
|---|---|---|
| `gap` | Keyword gap mode | keyword where a competitor beats you |
| `ranking` | Rankings mode | keyword a site ranks for |
| `competitor` | Competitors mode | competing site |
| `overview` | Every mode | site looked up (keyword counts, traffic, traffic value) |

The Console shows a separate table view for each type.

***

### Supported inputs

The actor works from **domains**, not URLs. Anything around the domain is removed, so `https://www.moz.com/blog?ref=x` becomes `moz.com`. Subdomains are kept as given: `blog.hubspot.com` is analysed as that subdomain.

| Mode | `domain` | `competitorDomains` |
|---|---|---|
| Keyword gap | your site | 0-5 competitors (empty = top 3 picked for you) |
| Rankings | first site | up to 24 more sites |
| Competitors | your site | not used |

**Not supported:** single-page (URL-level) analysis, paid search (ads) keywords, and countries without Google ranking data (the Country list only shows the 88 that have it).

***

### Use cases

| Who | What they do with it |
|---|---|
| Content teams | Turn `missing` gap keywords into the next quarter's article plan |
| SEO agencies | Run a gap for a prospect against their three biggest rivals for a sales deck |
| In-house SEO | Work through `isQuickWin` rows: pages already on page two that need a push |
| Founders | See which sites Google treats as your real competitors, which are not always the ones you expect |
| PPC teams | Find high-CPC keywords competitors get for free through organic rankings |
| Data teams | Feed rankings for a portfolio of sites into a dashboard or warehouse |

***

### How it works

<p align="center"><img src="https://raw.githubusercontent.com/muhamed-didovic/muhamed-didovic.github.io/main/assets/how-it-works-competitor-keywords.png" width="900" alt="How it works"></p>

1. You give a domain, an optional competitor list, a country and a row limit.
2. The actor checks your spending limit and trims the request to what it can pay for, before anything is fetched.
3. It looks up each site's ranking footprint (the `overview` row).
4. For a gap, it fetches what each competitor ranks for that you don't, and what you both rank for, filtered by volume and the competitor's position.
5. It merges everything into one row per keyword, drops brand terms and keywords you already lead on, scores the rest and saves them best first.

***

### Input configuration

| Field | Type | Default | What it does |
|---|---|---|---|
| `mode` | string | `gap` | `gap`, `rankings` or `competitors` |
| `domain` | string | required | The site to analyse, e.g. `moz.com` |
| `competitorDomains` | array | `[]` | Gap: up to 5 competitors. Rankings: up to 24 extra sites |
| `country` | string | `us` | Two-letter country code with Google ranking data |
| `language` | string | auto | Leave empty for the country's main language |
| `maxKeywords` | integer | `500` | Rows per site (rankings), gap rows (gap, at least 50) or competitors (max 100). 1-3,000 |
| `minSearchVolume` | integer | `0` | Skip keywords with fewer monthly searches |
| `maxCompetitorPosition` | integer | `20` | Gap only: a competitor must rank at or above this position |
| `excludeBrandTerms` | boolean | `true` | Drop keywords containing a compared site's name |
| `includeQuickWins` | boolean | `true` | Gap only: list quick wins first |

#### Keyword gap against named competitors

```json
{
    "mode": "gap",
    "domain": "moz.com",
    "competitorDomains": ["semrush.com", "ahrefs.com"],
    "country": "us",
    "maxKeywords": 500,
    "maxCompetitorPosition": 10
}
```

#### Keyword gap, competitors picked for you

```json
{ "mode": "gap", "domain": "moz.com", "minSearchVolume": 100 }
```

#### Rankings for several sites

```json
{
    "mode": "rankings",
    "domain": "backlinko.com",
    "competitorDomains": ["neilpatel.com"],
    "maxKeywords": 1000,
    "minSearchVolume": 100
}
```

#### Find competitors in the UK

```json
{ "mode": "competitors", "domain": "moz.com", "country": "gb", "maxKeywords": 25 }
```

***

### Output overview

A **gap row** has your position (`null` when you are not in the top 100), the best competitor's position and page, every competitor's position in `competitorPositions`, how many of them rank, the keyword's volume, CPC, difficulty and intent, and the score and quick-win flag. `gapType` is `missing` when you don't rank at all and `behind` when you rank below a competitor.

A **ranking row** has the site's position, ranking URL and page title, the change since the last check, estimated monthly traffic and what that traffic would cost in ads, the keyword's metrics, and the SERP features shown for the keyword.

A **competitor row** has how many keywords the competitor shares with your site, that overlap as a share of your keywords, its average position on them, and its own total keywords and traffic.

An **overview row** per site gives total ranked keywords, top-3, top-10 and page-two counts, estimated traffic, traffic value, and keywords gained and lost.

***

### Output samples

Real rows from runs on 2026-10-28, US market. Trimmed where marked.

#### Gap row (moz.com vs semrush.com and ahrefs.com)

```json
{
    "type": "gap",
    "keyword": "check backlinks",
    "gapType": "behind",
    "opportunityScore": 73,
    "isQuickWin": true,
    "yourDomain": "moz.com",
    "yourPosition": 14,
    "yourUrl": "https://moz.com/link-explorer",
    "bestCompetitor": "ahrefs.com",
    "bestCompetitorPosition": 1,
    "bestCompetitorUrl": "https://ahrefs.com/backlink-checker",
    "competitorsRanking": 2,
    "competitorsChecked": 2,
    "competitorPositions": { "semrush.com": 2, "ahrefs.com": 1 },
    "searchVolume": 90500,
    "cpc": 7.17,
    "keywordDifficulty": 51,
    "searchIntent": "informational",
    "competitionLevel": "LOW",
    "trendYearlyPct": 812,
    "country": "us",
    "language": "en"
}
```

#### Ranking row (backlinko.com, trimmed)

```json
{
    "type": "ranking",
    "domain": "backlinko.com",
    "keyword": "reddit popularity",
    "position": 2,
    "url": "https://backlinko.com/reddit-users",
    "title": "Reddit User and Growth Stats (Updated)",
    "previousPosition": 4,
    "positionChange": "up",
    "estimatedTraffic": 14661
}
```

#### Competitor row (moz.com, UK, trimmed)

```json
{
    "type": "competitor",
    "domain": "moz.com",
    "competitor": "semrush.com",
    "sharedKeywords": 3112,
    "overlapPct": 57.2,
    "avgPositionOnShared": 10.6,
    "trafficOnShared": 30514,
    "competitorTotalKeywords": 23290,
    "competitorTotalTraffic": 400289
}
```

#### Overview row (moz.com, trimmed)

```json
{ "type": "overview", "domain": "moz.com", "rankedKeywords": 22778, "top10Keywords": 7249, "estimatedTraffic": 261386 }
```

***

### Key output fields

**Gap:** `keyword`, `gapType`, `opportunityScore`, `isQuickWin`, `yourPosition`, `yourUrl`, `bestCompetitor`, `bestCompetitorPosition`, `bestCompetitorUrl`, `competitorPositions`, `competitorsRanking`, `competitorsChecked`

**Rankings:** `domain`, `keyword`, `position`, `url`, `title`, `previousPosition`, `positionChange`, `estimatedTraffic`, `trafficValueUsd`, `serpFeatures`, `isFeaturedSnippet`

**Competitors:** `competitor`, `sharedKeywords`, `overlapPct`, `avgPositionOnShared`, `trafficOnShared`, `competitorTotalKeywords`, `competitorTotalTraffic`, `competitorTrafficValueUsd`

**Keyword metrics (gap and rankings):** `searchVolume`, `cpc`, `keywordDifficulty`, `searchIntent`, `competitionLevel`, `trendYearlyPct`

**Overview:** `rankedKeywords`, `top3Keywords`, `top10Keywords`, `page2Keywords`, `estimatedTraffic`, `trafficValueUsd`, `newKeywords`, `lostKeywords`

**Every row:** `type`, `country`, `language`

***

### Pricing

Pay per event, no subscription:

| Event | Price | When |
|---|---|---|
| Run start | $0.02 | once per run |
| Site lookup | $0.02 | per site looked up (yours plus each competitor) |
| Ranked keyword | $0.002 | per `ranking` row |
| Gap keyword | $0.003 | per `gap` row |
| Competitor | $0.005 | per `competitor` row |

`overview` rows are included in the site lookup. A gap of moz.com against two competitors returning 500 keywords costs $0.02 + 3 × $0.02 + 500 × $0.003 = **$1.58**. 1,000 ranked keywords for one site cost **$2.04**.

If your maximum cost per run can't cover everything you asked for, the actor fetches only the rows it can bill and says so in the log. If it can't cover even one row (50 rows for a keyword gap), it stops before charging anything.

**Free Apify plan:** 100 rows per run, one site in rankings mode, one competitor in a gap, 5 runs per 24 hours. Prices are the same.

***

### FAQ

#### What does "missing" vs "behind" mean?

`missing`: at least one competitor ranks and you are not in the top 100. `behind`: you rank, but at least one competitor ranks higher. Keywords where you beat every competitor are left out, because they are not a gap.

#### How is the opportunity score calculated?

Search volume (log scale) counts for 40%, ease (100 minus difficulty) for 25%, the share of compared competitors that rank for 20%, and CPC for 15%. The result is rounded to 0-100. It is a sorting aid, not a forecast.

#### Where does the data come from?

From DataForSEO Labs, which tracks Google's top 100 organic results for a large keyword database in each country. Positions and traffic are estimates from that database, refreshed every few weeks, and will differ somewhat from what Semrush or Ahrefs show.

#### Why did my competitor list come back different from what I expected?

When you leave `competitorDomains` empty, competitors are picked by how many keywords they share with you in the chosen country. That is Google's view of your competition, and it can include publishers and tools you don't sell against. Name competitors yourself to compare with specific sites.

#### Why are some keywords with my brand missing?

`excludeBrandTerms` (on by default) drops keywords containing the name of any compared site, such as "moz pro" or "ahrefs pricing". Turn it off to keep them.

#### Can I analyse a single page instead of a whole domain?

No. The actor works at domain or subdomain level.

#### Does it cover paid search (Google Ads) keywords?

No, organic results only.

#### Why did a site come back with no rows?

There is no ranking data for it in the chosen country and language. Its `overview` row will have `null` counts, and the run's status message names it. Try the country where the site's audience is.

***

### Support

Found a bug or want a feature? Open an issue on the [Issues tab](https://apify.com/memo23/competitor-keywords/issues).

### Additional services

Need a scheduled weekly gap report, a different export shape, or this data joined with your own? I build custom versions. Open an issue or message me through Apify.

### Explore more scrapers

- **[Keyword Search Volume, CPC & Difficulty](https://apify.com/memo23/keyword-metrics)**: Google and Bing volume, CPC and difficulty for up to 1,000 keywords per run. Size the gap keywords you found here.
- **[Ahrefs Scraper](https://apify.com/memo23/ahrefs-scraper)**: Domain Rating, backlinks, referring domains and organic traffic.
- **[Semrush Scraper](https://apify.com/memo23/semrush-scraper)**: authority score, traffic, backlinks and top sites for a domain.
- **[Similarweb Scraper](https://apify.com/memo23/similarweb-scraper)**: traffic, rank, visits and audience for a list of domains.
- **[SEO Intelligence Suite](https://apify.com/memo23/seo-intelligence-suite)**: DR, traffic, AI-search visibility and Google presence in one call.

Full list at [apify.com/memo23](https://apify.com/memo23).

***

### 🤖 For AI Agents & LLM Apps

Compact reference for agents calling `memo23/competitor-keywords` through the [Apify MCP server](https://mcp.apify.com) or the Apify API.

**Purpose:** Google organic competitor research from DataForSEO Labs: keyword gap (one row per keyword, every competitor's position side by side), ranked keywords per site, and organic competitors.

**Minimal input (tested):**

```json
{ "mode": "gap", "domain": "moz.com", "competitorDomains": ["semrush.com"], "maxKeywords": 50 }
```

**Output:** rows tagged by `type`. `gap`: keyword, gapType (missing|behind), opportunityScore, isQuickWin, yourPosition, bestCompetitor, bestCompetitorPosition, competitorPositions {domain: position|null}, searchVolume, cpc, keywordDifficulty, searchIntent. `ranking`: domain, keyword, position, url, positionChange, estimatedTraffic, trafficValueUsd, searchVolume. `competitor`: competitor, sharedKeywords, overlapPct, avgPositionOnShared. `overview`: one per site, rankedKeywords, top10Keywords, estimatedTraffic.

**Behaviors an agent should know:**

- Always set `maxKeywords`. Gap and ranking rows are billed per row ($0.003 / $0.002), plus $0.02 per run and $0.02 per site.
- Empty `competitorDomains` in gap mode picks the top 3 competitors itself, which adds 3 site lookups.
- Gap rows come sorted best first: quick wins first (unless `includeQuickWins` is false), then by `opportunityScore`.
- Positions are Google top-100 only; `yourPosition: null` means not in the top 100.
- A site with no data still costs its lookup and returns an `overview` row with null counts.
- The run stops at the caller's maximum cost and fetches only what it can bill.

***

### ⚠️ Disclaimer

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Semrush Holdings, Inc., Ahrefs Pte. Ltd., Google LLC or DataForSEO. All trademarks mentioned are the property of their respective owners. Semrush and Ahrefs are named only to describe what kind of tool this is an alternative to.

The data comes from a licensed third-party SEO data provider and covers public Google search results only. It contains no personal data and nothing from behind any login. Rankings, traffic and volumes are estimates. Users are responsible for using the output in line with applicable law and their own contractual obligations.

***

### SEO Keywords

competitor keywords, keyword gap analysis, keyword gap tool, competitor keyword research, ranked keywords api, domain keywords, organic keywords checker, semrush alternative, ahrefs alternative, spyfu alternative, content gap analysis, seo competitor analysis, find seo competitors, quick win keywords, keyword opportunities, google rankings export, seo agency reporting, content strategy keywords, dataforseo labs, Apify seo actor

# Actor input Schema

## `mode` (type: `string`):

**Keyword gap** compares your site with up to 5 competitors and returns the keywords where they beat you, one row per keyword with every competitor's position side by side, scored and sorted by opportunity. **Rankings** lists the keywords each site ranks for, with position, traffic and keyword metrics. **Competitors** finds the sites that compete with yours in Google organic search.

## `domain` (type: `string`):

The site to analyse, as a domain (`moz.com`). Anything around it is removed: `https://www.moz.com/blog` becomes `moz.com`. In gap mode this is the site the competitors are compared against.

## `competitorDomains` (type: `array`):

**Keyword gap:** up to 5 competitors to compare with. Leave it empty and the 3 sites that share the most keywords with yours are picked for you (the giants that share keywords with everyone, like YouTube and Reddit, are skipped). **Rankings:** extra sites to list rankings for, up to 24. **Competitors:** not used.

## `country` (type: `string`):

The Google market to read rankings from. Only countries with ranking data are listed.

## `language` (type: `string`):

Leave on Auto to use the main language of the country. If the country has no rankings in the language you pick, its main language is used instead and the log says so.

## `maxKeywords` (type: `integer`):

**Rankings:** keywords per site, highest traffic first. **Keyword gap:** gap keywords returned, best opportunities first (at least 50; a lower number is raised to 50). **Competitors:** competitors returned (at most 100). You pay per row, so this also caps the cost. Free Apify plan: 100.

## `minSearchVolume` (type: `integer`):

Skip keywords searched fewer times a month than this. Filtered before the data is fetched, so rows below it are never charged.

## `maxCompetitorPosition` (type: `integer`):

**Keyword gap only.** A keyword counts as a gap only when a competitor ranks at or above this position. 10 keeps page one, 20 keeps the first two pages.

## `excludeBrandTerms` (type: `boolean`):

Drop keywords that contain a site's own name, such as "moz pro" or "semrush login". Nobody can win those, so they only crowd the list. The name is taken from each domain.

## `includeQuickWins` (type: `boolean`):

**Keyword gap only.** Put keywords where you rank 11-20 and a competitor holds the top 3 at the top of the results. Every gap row has an `isQuickWin` flag either way.

## Actor input object example

```json
{
  "mode": "gap",
  "domain": "moz.com",
  "competitorDomains": [
    "semrush.com",
    "ahrefs.com"
  ],
  "country": "us",
  "language": "",
  "maxKeywords": 500,
  "minSearchVolume": 0,
  "maxCompetitorPosition": 20,
  "excludeBrandTerms": true,
  "includeQuickWins": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domain": "moz.com",
    "competitorDomains": [
        "semrush.com",
        "ahrefs.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("memo23/competitor-keywords").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "domain": "moz.com",
    "competitorDomains": [
        "semrush.com",
        "ahrefs.com",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("memo23/competitor-keywords").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domain": "moz.com",
  "competitorDomains": [
    "semrush.com",
    "ahrefs.com"
  ]
}' |
apify call memo23/competitor-keywords --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,memo23/competitor-keywords"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/9xMeskR7FqxinbDqV/builds/ufW4scGctKZLTgsKP/openapi.json
