# Competitor Domain Keywords & Traffic (`datapulsehq/domain-keywords-traffic`) Actor

Competitor keywords and domain traffic by country: the domain keywords a site ranks for on Google (ranked keywords with position, search volume, CPC, keyword difficulty, estimated traffic and search intent), or an organic traffic estimate. Up to 1,000 domains per run. Not affiliated with Google.

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

## Pricing

from $0.51 / 1,000 keyword rows

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Domain Keywords & Traffic

Get the Google keywords a domain ranks for, or an estimate of its organic search traffic, for up to 1,000 domains per run.

The Actor has two modes:

- Ranked keywords: one row per domain and keyword, with the Google position, monthly search volume, CPC, estimated traffic from that keyword and search intent.
- Traffic overview: one row per domain, with estimated organic traffic, the number of ranking keywords, and estimated paid traffic and paid keywords. Optionally with the number of keywords per position range.

Typical uses are competitor research, finding keywords a competitor gets traffic from, sizing a list of domains by search traffic, and tracking a few domains over time.

### What the traffic numbers mean

Traffic is an estimate of organic search traffic from Google, not total site traffic. For every keyword a domain ranks for, the monthly search volume is multiplied by the expected click-through rate for its position, and the results are added up. Direct visits, social, email, referrals and other search engines are not included. Use the numbers to compare domains and spot trends, not as exact visit counts.

### Input

| Field | Default | Notes |
|---|---|---|
| `domains` | example | Up to 1,000 domains or URLs. `https://`, `www.` and paths are removed, duplicates are dropped, invalid entries are listed in the summary and not charged. Subdomains are kept as given. Without this field the Actor runs a small example (hubspot.com, up to 20 keywords). |
| `mode` | `rankedKeywords` | `rankedKeywords` or `trafficOverview`. |
| `location` | United States | Country name, two-letter code, location code, or `Worldwide` for all countries. Leaving the field out means the United States; an empty value stops the run with a message. |
| `language` | `en` | Language code or name, or `all` for every language. |
| `maxKeywordsPerDomain` | 100 | Ranked keywords: the keywords that bring the most estimated traffic come first. Up to 10,000. The input form starts at 20, so a first run with the three example domains returns up to 60 rows and costs at most $0.18 ($0.051 for the rows, $0.075 for the three domain lookups and $0.05 for the run). Leave the field out of an API call to get 100. |
| `minSearchVolume` | 0 | Ranked keywords: skip keywords with fewer monthly searches. |
| `maxPosition` | 100 | Ranked keywords: skip keywords where the domain ranks lower than this. 10 means first page only. |
| `includePositionDistribution` | false | Traffic overview: add keyword counts per position range and new/up/down/lost counts. Slower, one lookup per domain, and $0.027 extra per domain (see Pricing). |
| `useCache` | true | Reuse results for the same domain and settings from earlier runs. |
| `cacheTtlDays` | 7 | Maximum age of cached results. The ranking data is updated about once a week. |
| `includeNoDataRows` | false | Traffic overview: also output a free row for domains without data. |
| `pushSummaryToDataset` | false | Append the run summary to the dataset. |

With `"location": "Worldwide"` (also accepted as `global` or `all`) the ranked keywords come from all covered countries in one run. They are not merged into one figure: each row is one keyword in one country, and its `location` and `locationCode` name that country, so the same keyword can appear once per country with that country's search volume. The rows with the highest estimated traffic come first. The language you set is still applied; with `"language": "all"` keywords in every language are included and each row names its language. The run summary shows `Worldwide`. In traffic overview mode, a worldwide estimate covers all available locations; how the location data is combined is not documented in detail. A domain usually ranks for more keywords worldwide than in one country, so `maxKeywordsPerDomain` and the maximum charge per run matter more here.

Example:

```json
{
    "mode": "rankedKeywords",
    "domains": ["hubspot.com", "mailchimp.com", "notion.so"],
    "location": "United States",
    "language": "en",
    "maxKeywordsPerDomain": 100,
    "maxPosition": 10
}
```

The filters are applied before the data is fetched, so you only pay for rows that pass them.

### Supported countries and languages

Ranking data is available for the countries that the data source tracks on Google, including every country in the table below, plus Hong Kong and Taiwan. Regions and cities are not supported, and neither are Russia and Belarus. A country can be given by name, two-letter code or location code, and the language by code or name. A location or language without data stops the run as invalid input without a charge. Some examples:

| Country | `location` | Location code | Typical `language` |
|---|---|---|---|
| United States | `us` | 2840 | `en` |
| United Kingdom | `uk` | 2826 | `en` |
| India | `in` | 2356 | `en` or `hi` |
| Germany | `de` | 2276 | `de` |
| France | `fr` | 2250 | `fr` |
| Brazil | `br` | 2076 | `pt` |
| Spain | `es` | 2724 | `es` |
| Mexico | `mx` | 2484 | `es` |
| Indonesia | `Indonesia` | 2360 | `id` |
| Japan | `jp` | 2392 | `ja` |

Competitor keywords of a German site, German keywords only:

```json
{
    "mode": "rankedKeywords",
    "domains": ["zalando.de"],
    "location": "Germany",
    "language": "de",
    "maxKeywordsPerDomain": 100
}
```

Set `"location": "Worldwide"` to get the keywords a domain ranks for in all covered countries in one run, and add `"language": "all"` to include every language. In Worldwide mode the same keyword can appear several times, once per country (and per language with `all`): each row names its country in `location` and `locationCode` and its language in `language` and `languageCode`, while the run summary says `Worldwide`. See Input above for details.

### Output

Ranked keywords, one row per domain and keyword:

```json
{
    "domain": "mailchimp.com",
    "keyword": "mailchimp",
    "position": 1,
    "isNewRanking": false,
    "searchVolume": 368000,
    "cpc": 2.68,
    "competitionLevel": "MEDIUM",
    "keywordDifficulty": 45,
    "etv": 211692.97,
    "intent": "navigational",
    "secondaryIntents": "informational",
    "location": "United States",
    "locationCode": 2840,
    "language": "English",
    "languageCode": "en",
    "source": "live",
    "fetchedAt": "2026-10-21T21:22:47.782Z"
}
```

`position` is the position among organic results. `etv` is the estimated monthly traffic the domain gets from this keyword. `intent` is informational, navigational, commercial or transactional, and null when the keyword has no intent data. `cpc` is in USD and null when there is no ad data.

Traffic overview, one row per domain:

```json
{
    "domain": "hubspot.com",
    "organicEtv": 497381.26,
    "organicKeywords": 298693,
    "paidEtv": 6363.78,
    "paidKeywords": 17,
    "organicPos1": 5441,
    "organicPos2_3": 8028,
    "organicPos4_10": 36987,
    "organicPos11_20": 68394,
    "organicTop3": 13469,
    "organicTop10": 50456,
    "estimatedOrganicTrafficCost": 6654008.09,
    "newKeywords": 138855,
    "upKeywords": 74290,
    "downKeywords": 71188,
    "lostKeywords": 151706,
    "hasPositionDistribution": true,
    "location": "United States",
    "hasData": true
}
```

The position range fields (`organicPos1` to `organicPos91_100`, `organicTop3`, `organicTop10`), `estimatedOrganicTrafficCost` and the new/up/down/lost counts are only filled when `includePositionDistribution` is on, otherwise they are null. `estimatedOrganicTrafficCost` is what the organic traffic would cost as Google Ads clicks, in USD.

The dataset has two table views, Ranked keywords and Traffic overview.

### Run summary

Every run saves a `SUMMARY` record in the default key-value store: valid, invalid and duplicate domains (with the reason each invalid entry was rejected), domains without data, rows delivered, cache hits, charged events, rate-limit waits and the run status (`succeeded`, `failed` or `invalid_input`). If the input has nothing the Actor can use (no valid domains, every domain rejected by the data source, an unknown location or language, or a maximum charge per run too low for one domain), the run ends as succeeded with an empty dataset, nothing is charged, and the status message says what was wrong and how to fix it.

### Pricing

Pay per event:

- $0.00085 per keyword row in ranked keywords mode ($0.85 per 1,000 rows).
- $0.025 per domain looked up in ranked keywords mode, once per domain, also when the domain has no ranked keywords. Domains answered from the cache, invalid domains and failed lookups do not pay it.
- $0.0045 per domain in traffic overview mode ($4.50 per 1,000 domains).
- $0.027 per domain for the position distribution, on top of the $0.0045, only when `includePositionDistribution` is on and position data is returned for that domain.
- $0.05 once per run, only when the run fetches new data. A run answered fully from the cache does not pay it.

These are the prices on the Apify free plan. On paid plans the row and domain prices are lower, down to $0.00051 per keyword row, $0.00315 per domain overview and $0.0198 per position distribution on Gold and higher. The domain lookup and the run start cost the same on every plan.

Invalid domains and rows removed by your filters are not charged. In ranked keywords mode, every domain looked up live pays the $0.025 domain lookup, also when it returns no rows (for example a small or new domain, or a domain whose keywords are all removed by your filters); keyword rows are charged only for the rows returned. In traffic overview mode there is no domain lookup fee: domains without data are free, and a domain that comes back without position data is charged only the $0.0045. Rows from the cache are charged like fresh rows.

Examples:

- 1 domain with 1,000 keywords: $0.85 plus $0.025 plus $0.05, $0.925 in total.
- 3 domains with 100 keywords each: 300 rows for $0.255, plus $0.075 for the domain lookups and $0.05, $0.38 in total.
- 10 domains with 1,000 keywords each: 10,000 rows for $8.50, plus $0.25 and $0.05, $8.80 in total.
- Traffic overview for 100 domains: $0.45 plus $0.05, $0.50 in total.
- Traffic overview with position distribution for 100 domains: $0.45 plus $2.70 plus $0.05, $3.20 in total.

If you set a maximum charge per run, the Actor requests only as much data as that limit can pay for and stops cleanly when it is reached. The skipped domains are listed in the summary.

### Apify free plan limit

Users on the Apify free plan get up to 20 rows per run. The Actor looks up the first 20 domains of the list and names the rest under `freePlanLimit.skipped` in the run summary. In ranked keywords mode the 20 keyword rows are taken from those domains in the order you list them, and in traffic overview mode each domain is one row. The run still finishes as succeeded, and its status message says that the free plan limit was reached.

This limit was set by the developer of this Actor, not by Apify. The keyword and traffic data is paid per request, and runs by free plan users bring the developer no revenue. Any paid Apify plan removes the limit, and nothing else about the run changes.

### Run time and limits

Small runs finish in a few seconds. Very large runs (hundreds of domains with thousands of keywords each) take longer, because requests to the data source are rate limited. The Actor spaces its requests, waits and retries when a rate limit is hit, and records the waiting time in the summary.

If a run is restarted or migrated, domains that were already delivered are not fetched or charged again.

### Notes

- Data covers Google organic results. Keywords outside the top 100 are not included.
- Small or new domains often have no data. They are listed in the summary. In ranked keywords mode such a domain still pays the $0.025 domain lookup when it is looked up live, but no keyword rows. In traffic overview mode it is not charged.
- Use the domain the site actually serves. For example, if a company moved to a new domain, the old one may show far fewer keywords.
- Subdomains are treated as separate targets: `blog.example.com` returns only that subdomain.

### API

```bash
curl -X POST "https://api.apify.com/v2/acts/datapulsehq~domain-keywords-traffic/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "trafficOverview", "domains": ["hubspot.com", "mailchimp.com"]}'
```

This Actor is an independent tool and is not affiliated with, endorsed by, or sponsored by Google.

# Actor input Schema

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

<b>Ranked keywords</b>: one row per domain and keyword it ranks for in Google.<br><b>Traffic overview</b>: one row per domain with estimated organic and paid search traffic.

## `domains` (type: `array`):

Up to 1,000 domains or URLs. https://, www. and paths are removed (https://www.hubspot.com/pricing becomes hubspot.com). Subdomains such as blog.hubspot.com are kept. Duplicates are removed and invalid entries are listed in the run summary. Users on the Apify free plan get the first 20 domains per run. If the field is left out, the Actor runs a small example: hubspot.com with up to 20 keywords.

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

Country name (e.g. 'United States'), two-letter code ('us') or location code (2840). Enter 'Worldwide' for data across all countries. Leave the field out for the United States; an empty value is rejected.

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

Language code (e.g. 'en') or name ('English'). Enter 'all' for keywords in every language; most useful together with location 'Worldwide'.

## `maxKeywordsPerDomain` (type: `integer`):

Ranked keywords mode: the highest-traffic keywords first, up to this many per domain. Users on the Apify free plan get at most 20 rows per run in total.

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

Ranked keywords mode: only keywords with at least this many monthly searches.

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

Ranked keywords mode: only keywords where the domain ranks at this position or better (10 = first page).

## `includePositionDistribution` (type: `boolean`):

Traffic overview mode: add the number of keywords per position range (1, 2-3, 4-10, 11-20 ... 91-100) and new/up/down/lost counts. This needs one extra lookup per domain, so the run is slower.

## `useCache` (type: `boolean`):

Reuse results for the same domain and settings fetched in the last days (see cache age). Cached rows are delivered and charged like fresh rows.

## `cacheTtlDays` (type: `integer`):

Maximum age of cached results to reuse. The ranking data behind this Actor is refreshed about once a week.

## `includeNoDataRows` (type: `boolean`):

Traffic overview mode: also output (free of charge) a row for domains without any data. They are always listed in the run summary.

## `pushSummaryToDataset` (type: `boolean`):

Append the run summary as the last dataset item. Off by default; the summary is always saved in the key-value store as SUMMARY.

## Actor input object example

```json
{
  "mode": "rankedKeywords",
  "domains": [
    "hubspot.com",
    "mailchimp.com",
    "notion.so"
  ],
  "location": "United States",
  "language": "en",
  "maxKeywordsPerDomain": 20,
  "minSearchVolume": 0,
  "maxPosition": 100,
  "includePositionDistribution": false,
  "useCache": true,
  "cacheTtlDays": 7,
  "includeNoDataRows": false,
  "pushSummaryToDataset": false
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

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

No description

## `traffic` (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 = {
    "domains": [
        "hubspot.com",
        "mailchimp.com",
        "notion.so"
    ],
    "maxKeywordsPerDomain": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("datapulsehq/domain-keywords-traffic").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 = {
    "domains": [
        "hubspot.com",
        "mailchimp.com",
        "notion.so",
    ],
    "maxKeywordsPerDomain": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("datapulsehq/domain-keywords-traffic").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 '{
  "domains": [
    "hubspot.com",
    "mailchimp.com",
    "notion.so"
  ],
  "maxKeywordsPerDomain": 20
}' |
apify call datapulsehq/domain-keywords-traffic --silent --output-dataset

```

## MCP server setup

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

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/OpF3lzBulvXoRsf5X/builds/aozeYDmDLSDuJP7wb/openapi.json
