# Competitor Keyword Research API - SpyFu SEO, PPC, Ad History (`nabeelbaghoor/ppc-competitor-keyword-research-api`) Actor

Competitor keyword research with the SpyFu API: organic and PPC keywords any domain ranks for or buys, SEO and PPC competitors, ad copy history, top pages, monthly domain traffic stats, keyword volume, CPC, difficulty and Google top 100 SERP. Bring your own key.

- **URL**: https://apify.com/nabeelbaghoor/ppc-competitor-keyword-research-api.md
- **Developed by:** [Nabeel Hassan](https://apify.com/nabeelbaghoor) (community)
- **Categories:** SEO tools, Marketing, Developer tools
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 monthly domain stats returneds

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 Keyword Research API - SpyFu SEO, PPC, Ad History

Pull competitor keyword research from the SpyFu API with your own API key: the organic keywords any domain ranks for, the PPC keywords it buys, its ad copy history, SEO and PPC competitors, top pages and monthly traffic stats, as clean dataset rows.

Works with the SpyFu API (api.spyfu.com), the API behind SpyFu's SEO Research, PPC Research, Competitors, Ad History, Keyword Research and Domain Overview tools. The actor turns a list of domains or keywords into deduplicated rows you can download as JSON, CSV or Excel, schedule, or feed into SEO dashboards, PPC audits, competitor reports, Google Sheets, Looker Studio and AI agents. Bring your own API key.

BYOK, not live-tested: every endpoint, parameter and enum comes from SpyFu's published OpenAPI reference at developer.spyfu.com, and every path was confirmed to exist with a deliberately invalid key, but the actor has not been run against a live SpyFu account.

### What it does

- **Domain organic keywords (SEO)**: the keywords a domain, subdomain, path or URL ranks for in Google, with rank, rank change, ranking URL, search volume, keyword difficulty, CPC, estimated SEO clicks and click share. Pick the analysis: most valuable, newly ranked, gained or lost ranks, gained or lost clicks, just made page one or just fell off. Add a compare domain to see its rank on the same keywords.
- **Most successful PPC keywords** and **New PPC keywords**: the Google Ads keywords a domain has bought most successfully over the past year, or started buying recently, with volume, CPC by match type, monthly cost, clicks and the other advertisers.
- **Paid search ads for a domain**: the ads a domain shows, with keyword, ad position, ad title and body, advertiser count and the date seen.
- **Organic SEO competitors** and **PPC competitors**: the domains sharing a domain's organic or paid keywords, with common keyword counts.
- **Top organic pages**: the pages that earn the most organic traffic, or new pages gaining traffic, with keyword count, estimated clicks and top keyword.
- **Domain SEO and PPC stats**: the latest month, or the last N months, of organic keywords, organic clicks and value, paid clicks, ad budget, ads purchased, average ranks and domain strength. **Domain stats history** returns every month SpyFu has tracked.
- **Domain ad history** and **Keyword ad history**: historical ad copy for an advertiser, or the advertisers and ads a keyword has carried, with position and date.
- **Keyword research**: phrase match, questions, also buys ads for, also ranks for, or transactional keywords for a seed keyword.
- **Keyword metrics**: search volume, live search volume, ranking difficulty, CPC and monthly cost by match type, click shares, SERP features and the first organic result for a keyword list.
- **Keyword SERP top 100**: the Google organic results for a keyword with rank, rank change, URL, title and domain.
- **28 Google countries**, from US, UK, DE and AU to JP, IN, BR and ZA.
- **Pay only for results**: refused and empty lookups are recorded as a free row that says why, never charged.

### How to get an API key

SpyFu includes API access with its Pro + AI plan (with a 40 USD monthly API credit) and its Team/Agency plan (100 USD monthly credit). Open your SpyFu account settings at spyfu.com/account/api (API Usage) and copy the **Secret Key**, not the API ID, into the API key field, or set the `DATA_API_KEY` environment secret. The actor sends it as the documented `api_key` query parameter. This actor never ships a shared key.

#### What a run costs at SpyFu

SpyFu bills per row returned, as a CPM per endpoint: 0.20 USD per 1,000 rows for competitors and keyword metrics, 0.40 to 0.50 USD for domain stats and SEO keywords and SERPs, 1.00 USD for keyword research, 2.00 USD for PPC keywords and paid ads, 3.00 USD for ad history and 5.00 USD for top pages. The monthly API credit is used first and the rest is billed to your card. `maxResults` caps both the rows here and the rows SpyFu bills, and every page asks only for the rows the run still needs.

### Input

| Field | Used by | What it does |
| --- | --- | --- |
| `mode` | all | `seoKeywords`, `ppcKeywords`, `newPpcKeywords`, `domainAds`, `seoCompetitors`, `ppcCompetitors`, `topPages`, `domainStats`, `domainStatsHistory`, `domainAdHistory`, `keywordResearch`, `keywordMetrics`, `keywordSerp` or `keywordAdHistory` |
| `domains` | domain modes | Domains, one per line, such as nike.com (SEO keywords, PPC keywords and top pages also take a URL or path) |
| `keywords` | keyword modes | Keywords, one per line, such as running shoes |
| `countryCode` | all | Google country: `US` (default), `UK`, `DE`, `AU` and 24 more |
| `seoSearchType` | domain organic keywords | `MostValuable` (default), `NewlyRanked`, `GainedRanks`, `LostRanks`, `GainedClicks`, `LostClicks`, `JustMadeIt`, `JustFellOff` |
| `compareDomain` | domain organic keywords | A second domain whose rank is added to each row |
| `topPagesType` | top pages | `MostTraffic` (default) or `New` |
| `keywordSearchType` | keyword research | `PhraseMatch` (default), `Questions`, `AlsoBuysAdsFor`, `AlsoRanksFor`, `Transactions` |
| `historyMonths` | domain stats | 0 for the latest month, or the number of recent months |
| `keywordFilter` | top pages, domain ad history | Keyword contains this text |
| `serpDepth` | keyword SERP | Results per keyword, 1 to 105 (default 100) |
| `includeTerms`, `matchAnyTerm`, `excludeTerms` | keyword lists | Terms a keyword must contain (all, or any) or must not contain |
| `volumeMin`, `volumeMax`, `difficultyMin`, `difficultyMax` | keyword lists, keyword metrics | Search volume and ranking difficulty ranges |
| `cpcMin`, `cpcMax`, `cpcMatchType` | SEO and PPC keywords, keyword research, keyword metrics | Cost per click range, by Broad, Exact or Phrase match |
| `rankMin`, `rankMax` | domain organic keywords | Google rank range, such as 1 to 10 for page one |
| `excludeHomepageKeywords` | SEO keywords, paid ads | Leave out keywords the homepage ranks for |
| `questionsOnly`, `transactionalOnly` | PPC keywords, keyword research, keyword metrics | Question or buying intent keywords only |
| `includeAdult` | keyword lists | Keep adult keywords, which SpyFu leaves out by default |
| `seoSortBy`, `keywordSortBy`, `adSortBy`, `competitorSortBy`, `adHistorySortBy`, `sortOrder` | list modes | Sort field and order |
| `maxResults` | all | Row ceiling for the run (default 100) |
| `requestsPerMinute` | all | Pacing (default 120) |
| `apiKey` | all | Your SpyFu Secret Key, stored as a secret |

An endpoint only receives the parameters its documentation lists; an input that belongs to another mode is left off with a warning.

Example input, the page one keywords a competitor gained clicks on in the US, with your own rank alongside:

```json
{
    "mode": "seoKeywords",
    "domains": ["semrush.com"],
    "seoSearchType": "GainedClicks",
    "compareDomain": "spyfu.com",
    "countryCode": "US",
    "rankMax": 10,
    "volumeMin": 500,
    "excludeTerms": ["semrush"],
    "maxResults": 100
}
```

### Example output

An example domain organic keyword row, shaped from SpyFu's documented response. The common columns come first; every field SpyFu returns follows unchanged.

```json
{
    "mode": "seoKeywords",
    "modeLabel": "Domain organic keywords",
    "recordType": "seoKeyword",
    "identifier": "semrush.com",
    "found": true,
    "countryCode": "US",
    "domain": "semrush.com",
    "keyword": "keyword research tool",
    "url": "https://www.semrush.com/analytics/keywordmagic/",
    "title": null,
    "rank": 3,
    "searchVolume": 9900,
    "difficulty": 78,
    "cpc": 12.4,
    "seoClicks": 1210,
    "period": null,
    "retrievedAt": "2026-10-14T09:00:00.000Z",
    "note": null,
    "topRankedUrl": "https://www.semrush.com/analytics/keywordmagic/",
    "rankChange": 2,
    "keywordDifficulty": 78,
    "broadCostPerClick": 12.4,
    "phraseCostPerClick": 11.9,
    "exactCostPerClick": 10.6,
    "seoClicksChange": 340,
    "totalMonthlyClicks": 7100,
    "percentMobileSearches": 22.5,
    "percentDesktopSearches": 77.5,
    "percentNotClicked": 28.1,
    "percentPaidClicks": 9.4,
    "percentOrganicClicks": 62.5,
    "broadMonthlyCost": 4210.0,
    "phraseMonthlyCost": 3890.0,
    "exactMonthlyCost": 2950.0,
    "paidCompetitors": 14,
    "rankingHomepages": 6,
    "yourRank": 5,
    "yourRankChange": -1,
    "yourUrl": "https://www.spyfu.com/keyword-research"
}
```

PPC keyword and keyword research rows carry `liveSearchVolume`, `rankingDifficulty`, CPC, monthly clicks and monthly cost by match type, `distinctCompetitors`, `serpFeaturesCsv` and `serpFirstResult`; paid ad rows carry `adPosition`, `adCount`, `title`, `bodyHtml` and `dateSearched`; competitor rows carry `domain`, `commonTerms` and `rank`; top page rows carry `keywordCount`, `estMonthlySeoClicks` and the top keyword with its position; domain stats rows carry `period` (YYYY-MM) with `monthlyOrganicClicks`, `monthlyOrganicValue`, `monthlyPaidClicks`, `monthlyBudget`, `totalOrganicResults`, `totalAdsPurchased`, `averageOrganicRank`, `averageAdRank` and `strength`; ad history rows carry `title`, `body`, `position`, `searchDateId` and `adId`. `cpc` is the broad match cost per click and `difficulty` is SpyFu's keyword (ranking) difficulty.

### FAQ

#### What is the SpyFu API?

SpyFu is a competitor research tool for SEO and Google Ads that has tracked organic rankings and paid ads since 2007 across 28 Google country versions. Its REST API at api.spyfu.com serves the same domain, keyword, competitor and ad history data as the SpyFu web app, billed per row returned.

#### Do I need my own SpyFu API key?

Yes. The actor calls SpyFu with your own Secret Key, and SpyFu bills the rows to your own account. A run without a key ends straight away with a message that says where to find it, and nothing is charged.

#### How do I find the keywords a competitor ranks for?

Use the domain organic keywords mode with the competitor in `domains`. Most valuable lists the keywords that bring it the most organic clicks; the other analyses show what it gained, lost or newly ranks for this month. Add `rankMax: 10` for page one, or your own site in `compareDomain` to see where it outranks you.

#### How do I see a competitor's Google Ads keywords and ad copy?

Most successful PPC keywords lists the keywords it has bought longest and most profitably, New PPC keywords what it just started bidding on, Paid search ads the ads it shows now, and Domain ad history its ad copy over the years.

#### How do I find a site's competitors?

Organic SEO competitors and PPC competitors list the domains that share the most organic or paid keywords with it, with the number of common keywords, up to 550 per domain.

#### How do I get search volume, CPC and keyword difficulty for a keyword list?

Use the keyword metrics mode and paste the keywords into `keywords`, one per line. Each keyword becomes one row with search volume, ranking difficulty, CPC and monthly cost by match type and click shares. For new ideas, use keyword research with phrase match, questions, also ranks for or also buys ads for.

#### What do I pay?

Pay per result: 0.005 USD per keyword, competitor, top page or SERP row, 0.008 USD per paid ad or ad history row, and 0.01 USD per month of domain stats. Refused and empty lookups are never charged. SpyFu bills its own per row API charges to your SpyFu account separately.

#### What happens if my key is wrong or my plan has no API access?

A rejected key (HTTP 401, "Secret Key Invalid") or a plan without API access ends the run cleanly with a status message that explains what to change. Rate limits (HTTP 429) are waited out using SpyFu's Retry-After header. A domain or keyword SpyFu refuses becomes a free row with the API's own words, and the run moves on.

#### How deep can a list go?

SpyFu's keyword and ad lists page up to row 10,000 per domain or keyword, competitors up to 550, and the SERP up to 105 results. The actor pages automatically and stops at `maxResults`.

### Pricing

| Event | Price | Charged for |
| --- | --- | --- |
| `keyword-row` | 0.005 USD | Each organic or PPC keyword, competitor, top page or SERP result |
| `ad-row` | 0.008 USD | Each paid search ad or historical ad |
| `domain-stats-row` | 0.01 USD | Each month of domain SEO and PPC statistics |

### Keyword map

SpyFu API, competitor keyword research, competitor analysis API, PPC keywords, Google Ads competitor research, ad copy history, ad spy tool, SEO keywords API, organic keywords, keyword gap, domain overview, website traffic estimate, SEO competitors, PPC competitors, top pages, keyword research API, search volume, keyword difficulty, CPC data, SERP API, Google top 100, Semrush alternative, Ahrefs alternative.

### Terms and data use

Use of SpyFu is governed by SpyFu's terms and conditions (spyfu.com/termsconditions). They allow you to download, store and archive information from the service for your own use, and say that without SpyFu's written consent you may not sell, publish, distribute or retransmit its content, display it on another website or service, or use an archive of it to operate an automated reporting system. Run this actor with your own key, for your own SEO and PPC research, keep the results for your own use, and ask SpyFu before you republish or resell the data. This actor is not affiliated with or endorsed by SpyFu.

# Actor input Schema

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

Domain modes take domains: the organic keywords a domain ranks for (with gains, losses and new rankings), the PPC keywords it buys, the ads it shows, its SEO and PPC competitors, its top pages, its monthly SEO and PPC stats, and its ad history. Keyword modes take keywords: keyword research, metrics for a keyword list, the Google top 100 and the advertisers and ads for a keyword.

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

Domain modes only. Domains to analyse, one per line, such as nike.com. A scheme, www. and any path are removed, except for Domain organic keywords, PPC keywords and Top organic pages, which also read a URL, subdomain or path such as nike.com/running/.

## `keywords` (type: `array`):

Keyword modes only. Keywords to look up, one per line, up to 200 characters each.

## `countryCode` (type: `string`):

The Google country version SpyFu reads, such as US for google.com or DE for google.de. Search volume, rankings and ads all come from that country.

## `seoSearchType` (type: `string`):

Domain organic keywords only. Which SpyFu keyword analysis to run; the movement analyses compare with the previous month.

## `compareDomain` (type: `string`):

Domain organic keywords only. A second domain, such as a competitor; each row then also carries that domain's rank, rank change and URL (yourRank, yourRankChange, yourUrl).

## `topPagesType` (type: `string`):

Top organic pages only.

## `keywordSearchType` (type: `string`):

Keyword research only. Phrase match finds thematically related keywords, Questions finds question keywords, Also buys ads for finds what the seed keyword's advertisers also buy, Also ranks for finds what its top ranking sites also rank for, and Transactional finds keywords with buying intent.

## `historyMonths` (type: `integer`):

Domain SEO and PPC stats only. 0 returns the latest month; a higher number returns that many recent months, one row each.

## `keywordFilter` (type: `string`):

Top organic pages and Domain ad history only. Only pages or ads for keywords containing this text.

## `serpDepth` (type: `integer`):

Keyword SERP top 100 only. How many ranking results to read per keyword (1 to 105).

## `includeTerms` (type: `array`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research. Only keywords that contain these terms (all of them, or any with the next option).

## `matchAnyTerm` (type: `boolean`):

With Keyword must contain: keep keywords containing any of the terms instead of all of them.

## `excludeTerms` (type: `array`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research. Leave out keywords containing these terms, such as brand names.

## `volumeMin` (type: `integer`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research and Keyword metrics. Only keywords with at least this monthly Google search volume.

## `volumeMax` (type: `integer`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research and Keyword metrics. Only keywords with at most this monthly Google search volume.

## `difficultyMin` (type: `integer`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research and Keyword metrics. Only keywords with a ranking difficulty of at least this (0 to 100).

## `difficultyMax` (type: `integer`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research and Keyword metrics. Only keywords with a ranking difficulty of at most this (0 to 100).

## `cpcMin` (type: `number`):

Domain organic keywords, PPC keywords, Keyword research and Keyword metrics. Only keywords with a cost per click of at least this.

## `cpcMax` (type: `number`):

Domain organic keywords, PPC keywords, Keyword research and Keyword metrics. Only keywords with a cost per click of at most this.

## `cpcMatchType` (type: `string`):

Which match type's cost per click the CPC range reads. Sent only with a CPC range.

## `rankMin` (type: `integer`):

Domain organic keywords only. Only rankings at this Google position or lower down.

## `rankMax` (type: `integer`):

Domain organic keywords only. Only rankings at this Google position or higher up, such as 10 for page one.

## `excludeHomepageKeywords` (type: `boolean`):

Domain organic keywords and Paid search ads only. Leave out keywords the homepage ranks for.

## `questionsOnly` (type: `boolean`):

PPC keywords, Keyword research and Keyword metrics only. Keep only question keywords.

## `transactionalOnly` (type: `boolean`):

PPC keywords, Keyword research and Keyword metrics only. Keep only keywords with buying intent.

## `includeAdult` (type: `boolean`):

Domain organic keywords, Most successful PPC keywords, New PPC keywords, Paid search ads and Keyword research and Keyword metrics. SpyFu leaves out adult keywords by default; tick to keep them.

## `seoSortBy` (type: `string`):

Domain organic keywords only. Provider default is search volume.

## `keywordSortBy` (type: `string`):

PPC keywords and Keyword research only.

## `adSortBy` (type: `string`):

Paid search ads only.

## `competitorSortBy` (type: `string`):

SEO and PPC competitors only. Provider default is rank.

## `adHistorySortBy` (type: `string`):

Domain ad history only.

## `sortOrder` (type: `string`):

Keyword, ad, competitor and top page lists.

## `maxResults` (type: `integer`):

The most rows this run returns and charges for. SpyFu also bills your account per row returned (0.20 to 5.00 USD per 1,000 rows by endpoint), so keep this modest.

## `requestsPerMinute` (type: `integer`):

Pacing. SpyFu limits each endpoint per second (2 a second for top pages, 5 to 1,000 for others); a rate limit answer is waited out and retried.

## `apiKey` (type: `string`):

Your own SpyFu Secret Key, from your SpyFu account (spyfu.com/account/api, under API Usage). Stored as a secret and sent only as the api_key parameter to api.spyfu.com; it is never logged. You can instead set the DATA_API_KEY environment secret.

## Actor input object example

```json
{
  "mode": "seoKeywords",
  "domains": [
    "spyfu.com"
  ],
  "countryCode": "US",
  "seoSearchType": "MostValuable",
  "topPagesType": "MostTraffic",
  "keywordSearchType": "PhraseMatch",
  "historyMonths": 0,
  "serpDepth": 100,
  "matchAnyTerm": false,
  "cpcMatchType": "Broad",
  "excludeHomepageKeywords": false,
  "questionsOnly": false,
  "transactionalOnly": false,
  "includeAdult": false,
  "seoSortBy": "default",
  "keywordSortBy": "default",
  "adSortBy": "default",
  "competitorSortBy": "default",
  "adHistorySortBy": "default",
  "sortOrder": "Descending",
  "maxResults": 100,
  "requestsPerMinute": 120
}
```

# Actor output Schema

## `records` (type: `string`):

One row per keyword, ad, competitor, page, monthly stat or SERP result.

# 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": [
        "spyfu.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nabeelbaghoor/ppc-competitor-keyword-research-api").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": ["spyfu.com"] }

# Run the Actor and wait for it to finish
run = client.actor("nabeelbaghoor/ppc-competitor-keyword-research-api").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": [
    "spyfu.com"
  ]
}' |
apify call nabeelbaghoor/ppc-competitor-keyword-research-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nabeelbaghoor/ppc-competitor-keyword-research-api"
        }
    }
}
```

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/LljuSCVnXiOvbxNMb/builds/48FGZu3JG9TgqNqgQ/openapi.json
