# Competitor Keywords, Keyword Gap & Competitor Finder (`pravus/competitor-keywords`) Actor

Every Google keyword any website ranks for, the keywords competitors rank for and you don't, and who your organic competitors are. Position, URL, traffic, volume, CPC, difficulty, intent. Semrush and Ahrefs alternative: $2 per 1,000 keywords, $0.60 after 500.

- **URL**: https://apify.com/pravus/competitor-keywords.md
- **Developed by:** [Pravus](https://apify.com/pravus) (community)
- **Security:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 ranked 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 & Competitor Finder

Three competitor-research tools in one, pay per use. A Semrush and Ahrefs alternative with no subscription and no SEO-suite seat:

1. **Ranked keywords:** every Google keyword any website, or a single page, ranks for, with position, ranking URL, estimated monthly visits, search volume, CPC, keyword difficulty and search intent.
2. **Keyword gap:** the keywords your competitors rank for and **you don't**, with brand terms filtered out so you get keywords you can actually target. Or the keywords you both rank for, with both positions side by side.
3. **Competitor finder:** who your real organic competitors are, ranked by shared keywords, with their total keywords and traffic. Giant sites like Wikipedia and Amazon are left out.

- **Up to 10,000 keywords per domain and 100 domains per run**, sorted by the traffic each keyword brings.
- **Organic, paid ads, featured snippets, local pack and Google AI Overview citations.**
- **Free domain overview** for every site: total ranked keywords, estimated monthly organic traffic and its ad value, top-3 and top-10 counts, and new and moving keywords.
- **$2 per 1,000 keywords**, dropping to **$0.60 per 1,000** after the first 500 of a run. Competitors are $0.003 each.

Built for SEO teams, content strategists, agencies doing competitor audits, PPC managers, and **AI agents** that need competitor data mid-task (works over Apify's MCP server in Claude, Cursor, ChatGPT and other clients).

### What you get for each keyword

| Field | Meaning |
|---|---|
| `target` | The domain or page you asked about |
| `keyword` | A keyword it ranks for |
| `position` | Its Google position among results of that type (1 = top organic result) |
| `positionChange`, `previousAbsolutePosition` | `new`, `up`, `down` or `same` since the previous check |
| `url`, `title` | The page that ranks and its title |
| `estimatedTraffic` | Estimated monthly visits this ranking brings |
| `trafficValueUsd` | What that traffic would cost per month as Google Ads clicks |
| `searchVolume`, `monthlySearches` | Average monthly Google searches, plus the last 12 months |
| `trendMonthlyPct`, `trendQuarterlyPct`, `trendYearlyPct` | Percentage change in search volume |
| `cpc`, `competition`, `competitionLevel` | Google Ads cost per click (USD) and competition |
| `keywordDifficulty` | How hard it is to rank in the top 10 organically (0–100) |
| `searchIntent` | informational, commercial, transactional or navigational |
| `resultType` | organic, paid, featured_snippet, ai_overview_reference or local_pack |
| `serpFeatures` | Which Google result features appear for the keyword |
| `comparedWith`, `gapType`, `yourPosition`, `yourUrl` | Keyword gap: your domain, `missing` or `shared`, and (shared) where you rank |
| `found` | `false` on rows that explain a domain with no results, or an invalid or rejected target |

### What you get for each competitor (competitor finder)

| Field | Meaning |
|---|---|
| `competitor` | A domain competing with your target for the same keywords |
| `sharedKeywords` | Keywords both sites rank for |
| `avgPositionOnShared` | The competitor's average position on those keywords |
| `competitorTotalKeywords`, `competitorTop10Keywords` | All keywords the competitor ranks for, and how many are on page one |
| `competitorEstimatedTraffic`, `competitorTrafficValueUsd` | The competitor's estimated monthly organic visits and their ad value |
| `competitorTrafficOnShared`, `targetTrafficOnShared` | Estimated visits each site gets from the shared keywords: who wins the overlap |

The **Domain overview** output (`DOMAINS`) gives each site's totals for free: matching ranked keywords, estimated monthly traffic and traffic value, keywords in the top 3 and top 10, and how many are new, moved up or moved down, per ranking type.

### Examples

**A competitor's top 500 keywords by traffic**

```json
{
  "targets": ["competitor.com"],
  "maxKeywordsPerTarget": 500
}
```

**First-page keywords for several competitors, in the UK**

```json
{
  "targets": ["competitor-one.com", "competitor-two.co.uk", "competitor-three.com"],
  "location": "uk",
  "maxPosition": 10,
  "minSearchVolume": 100,
  "maxKeywordsPerTarget": 1000
}
```

**One page: what does a competitor's pricing page rank for?**

```json
{ "targets": ["https://competitor.com/pricing"], "sortBy": "position" }
```

**Keyword gap: what do my competitors rank for that I don't?**

```json
{
  "mode": "gap",
  "targets": ["competitor-one.com", "competitor-two.com"],
  "yourDomain": "my-site.com",
  "maxKeywordsPerTarget": 500,
  "minSearchVolume": 100
}
```

Brand keywords (theirs and yours) are excluded by default in gap mode. Set `"gapType": "shared"` for keywords you both rank for, with your position next to theirs.

**Who are my organic competitors?**

```json
{ "mode": "competitors", "targets": ["my-site.com"], "maxCompetitors": 50 }
```

Feed the result into a keyword gap run to see what each of them ranks for that you don't.

**Which questions does a site get cited for in Google's AI Overviews?**

```json
{ "targets": ["healthline.com"], "resultType": "ai_overview_reference", "keywordContains": "how" }
```

- `sortBy`: `traffic` (default, the keywords that matter most), `searchVolume` or `position`.
- `location`: a country name (`"Germany"`), a two-letter code (`"us"`, `"uk"`, `"de"`) or a numeric location code (`2840`).
- `language`: leave empty to use the country's main language (English for the US and UK, German for Germany...), or set `"en"`, `"es"` and so on. Countries with several languages (Belgium, Switzerland...) need it set for gap and competitor runs.
- `brandKeywords`: `"exclude"` skips keywords containing the site's brand name, `"include"` keeps them. The default, `"auto"`, excludes them in gap mode only.

### Output example

```json
{
  "target": "https://www.nike.com/w/sale-3yaep",
  "keyword": "nike outlet",
  "found": true,
  "resultType": "organic",
  "position": 1,
  "absolutePosition": 1,
  "previousAbsolutePosition": 4,
  "positionChange": "up",
  "url": "https://www.nike.com/w/sale-3yaep",
  "title": "Shop All Sale",
  "searchVolume": 301000,
  "estimatedTraffic": 60942.2,
  "trafficValueUsd": 59113.98,
  "cpc": 0.97,
  "competition": 1,
  "competitionLevel": "HIGH",
  "keywordDifficulty": 19,
  "searchIntent": "navigational",
  "trendMonthlyPct": 0,
  "trendQuarterlyPct": 50,
  "trendYearlyPct": -18,
  "monthlySearches": [{ "year": 2026, "month": 8, "searchVolume": 368000 }],
  "serpFeatures": ["organic", "local_pack", "people_also_ask", "related_searches", "knowledge_graph", "images"],
  "location": "United States",
  "language": "en",
  "dataUpdatedAt": "2026-07-30 03:41:45 +00:00"
}
```

**Competitor finder row** (nike.com, real data)

```json
{
  "target": "nike.com",
  "competitor": "dickssportinggoods.com",
  "found": true,
  "sharedKeywords": 314855,
  "avgPositionOnShared": 14.4,
  "competitorTotalKeywords": 1613684,
  "competitorEstimatedTraffic": 8962786,
  "competitorTrafficValueUsd": 5430338,
  "competitorTop10Keywords": 499933,
  "targetTrafficOnShared": 6015630,
  "competitorTrafficOnShared": 1470119,
  "location": "United States",
  "language": "en"
}
```

### Pricing

Pay per use, with no subscription:

| | Price |
|---|---|
| Domain data request (up to 1,000 keywords or competitors for one domain or page), charged per successful request | $0.04 |
| Ranked keyword: first 500 keywords of each run | $0.002 each ($2 per 1,000) |
| Ranked keyword: every keyword after the first 500 in a run | **$0.0006 each ($0.60 per 1,000)** |
| Competitor domain (competitor finder) | $0.003 each |

| Run | Cost |
|---|---|
| 1 domain, top 100 keywords | $0.24 |
| 1 domain, top 1,000 keywords | $1.34 |
| 10 competitors, top 100 keywords each | $1.70 |
| 1 domain, 10,000 keywords | $7.10 |
| Keyword gap vs 3 competitors, top 200 each | $1.18 |
| Your top 20 competitors | $0.10 |

- **Invalid domains, duplicates, and requests that fail are never charged.** A domain with no results (a brand-new site, or filters that match nothing) costs only the $0.04 request fee, and the free domain overview tells you why.
- **Free Apify plan:** try it on 1 domain with up to 20 keywords per run. Any paid Apify plan unlocks up to 100 domains and 10,000 keywords per domain.
- Set a **maximum cost per run** in the run options and the Actor stops *before* doing work that would exceed it, so you're never charged past your limit.

### Use it from code

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

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('pravus/competitor-keywords').call({
    targets: ['competitor.com'],
    maxKeywordsPerTarget: 1000,
    maxPosition: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const domains = await client.keyValueStore(run.defaultKeyValueStoreId).getRecord('DOMAINS');
```

### Use it from an AI agent (MCP)

Add the Apify MCP server to your client and the agent can call this Actor as a tool, for example: *"Find the 300 keywords that bring competitor.com the most traffic, keep the ones with difficulty under 30 where we could realistically rank, and group them into content topics."*

### Pair it with keyword research

To check volumes and difficulty for your own keyword list, or find new keyword ideas from seed terms, use [Keyword Research API: Search Volume, CPC, Difficulty & Intent](https://apify.com/pravus/keyword-overview).

### FAQ

**Where does the data come from?** A licensed SEO data provider that tracks Google results for millions of keywords. The database is updated weekly; `dataUpdatedAt` shows when each keyword's ranking was last checked. Search volume and CPC come from Google Ads data.

**How accurate is the traffic estimate?** `estimatedTraffic` is a model: search volume times the typical click-through rate for that position. It is best for comparing keywords and competitors, not as an exact visit count.

**Why are the totals different from Semrush or Ahrefs?** Every SEO database tracks a different set of keywords, so totals differ between tools. Relative rankings and the top keywords usually agree.

**What counts as a "brand keyword"?** Any keyword containing the domain's name, e.g. "adidas samba" for adidas.com. Hyphenated names match the spaced and joined spellings, so foot-locker.com excludes "foot locker" and "footlocker". If a site's name is an everyday word (apartments.com, cars.com), its "brand" keywords are its main topic: set `"brandKeywords": "include"` for those.

**Domain or page: which should I use?** A domain (`competitor.com`) covers every page on the site. A full URL (`https://competitor.com/blog/post`) covers that page only. A subdomain (`blog.competitor.com`) covers just that subdomain. Keyword gap and competitor finder work on domains.

**Is there a limit?** 100 domains per run, 10,000 keywords or 1,000 competitors per domain, and about 60,000 keywords per run in total. A run that reaches a limit stops cleanly, keeps everything it collected and lists what it didn't reach (not charged). Start another run for the rest.

**What happens with very large runs?** Big lists (for example 100 domains with 10,000 keywords each) can take longer than the default run timeout. The Actor stops cleanly shortly before the timeout, keeps everything it collected, and tells you which domains it didn't reach (those aren't charged). Raise the timeout in the run options, or split the list across runs.

# Actor input Schema

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

"keywords": every keyword the targets rank for. "gap": keywords the targets (your competitors) rank for and your site doesn't. "competitors": the domains competing with each target for the same keywords.

## `targets` (type: `array`):

Up to 100 per run. A domain ("example.com", or a subdomain like "blog.example.com") covers the whole site; a full URL ("https://example.com/pricing") covers that page only (keywords mode). In keyword gap mode, list your competitors here.

## `yourDomain` (type: `string`):

Keyword gap mode: your own site, like "example.com". You get the keywords the targets rank for and this site doesn't.

## `location` (type: `string`):

Country whose Google results to check. Use a country name ("United States", "Germany"), a two-letter code ("us", "uk", "de") or a DataForSEO location code (2840).

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

Optional. Leave empty to use the country's main language (English for the US and UK, French for France...). For countries with several languages (Belgium, Switzerland...) keywords mode then covers all of them; keyword gap and competitors need one set here. Use a two-letter code ("en", "es") or a language name.

## `maxKeywordsPerTarget` (type: `integer`):

Keywords and keyword gap modes: how many keywords to return for each domain, best first (up to 10,000).

## `maxCompetitors` (type: `integer`):

Competitors mode: how many competing domains to return for each target, most shared keywords first (up to 1,000).

## `sortBy` (type: `string`):

Keywords and gap modes. "traffic": keywords that bring the site the most estimated visits first (recommended). "searchVolume": biggest keywords first. "position": best rankings first. Competitors are always sorted by shared keywords.

## `gapType` (type: `string`):

"missing": keywords the competitor ranks for and you don't (content ideas). "shared": keywords you both rank for, with both positions (where they beat you).

## `resultType` (type: `string`):

Which Google results to include: organic results, paid ads, featured snippets, citations in Google's AI Overviews (keywords mode only), local pack (map) results, or all of them.

## `maxPosition` (type: `integer`):

Only count rankings at this position or better (e.g. 10 = first page of Google, 3 = top 3). In competitors mode, only rankings this high count towards shared keywords.

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

Keywords and gap modes: skip keywords with fewer monthly searches than this.

## `keywordContains` (type: `string`):

Optional, keywords and gap modes. Only keywords containing this text (case-insensitive), e.g. "pricing" or "how to".

## `brandKeywords` (type: `string`):

Keywords that contain the domain's brand name, e.g. "adidas samba" for adidas.com (and your own brand in gap mode). "auto" excludes them in keyword gap mode, where they can't be targeted, and keeps them otherwise.

## `excludeTopDomains` (type: `boolean`):

Competitors mode: leave out sites that compete with everyone (Wikipedia, Amazon, YouTube and similar), so you see real niche competitors.

## Actor input object example

```json
{
  "mode": "keywords",
  "targets": [
    "apify.com"
  ],
  "location": "United States",
  "maxKeywordsPerTarget": 100,
  "maxCompetitors": 20,
  "sortBy": "traffic",
  "gapType": "missing",
  "resultType": "organic",
  "maxPosition": 100,
  "minSearchVolume": 0,
  "brandKeywords": "auto",
  "excludeTopDomains": true
}
```

# Actor output Schema

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

Main results table for any mode (JSON). Keyword gap and competitors also have their own tables.

## `gap` (type: `string`):

Keyword gap mode: keywords the competitors rank for and you don't (or both rank for), as a table.

## `competitors` (type: `string`):

Competitors mode: competing domains with shared keywords and traffic, as a table.

## `allFields` (type: `string`):

Every field for every row, including 12 months of search volume history, trends and SERP features.

## `domains` (type: `string`):

Free per-domain totals: matching keywords or competitors, plus (keywords mode) estimated monthly traffic and its value, top-3 and top-10 counts, and new and moving keywords.

## `summary` (type: `string`):

Counts of keywords returned, empty, invalid and rejected domains, and charged events.

# 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 = {
    "targets": [
        "apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pravus/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 = { "targets": ["apify.com"] }

# Run the Actor and wait for it to finish
run = client.actor("pravus/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 '{
  "targets": [
    "apify.com"
  ]
}' |
apify call pravus/competitor-keywords --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pravus/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/XwaB6lQjB248SGPwY/builds/RTCGLay9CEheEVs8L/openapi.json
