# Keyword Coverage Gap Analyzer EN PL (`danielmichalek/keyword-gap-analyzer`) Actor

Compare phrase coverage across up to 100 supplied pages with English or Polish rules. Get document counts, original evidence and duplicate aliases in JSON and CSV. Send your own and reference texts to prepare a review report.

- **URL**: https://apify.com/danielmichalek/keyword-gap-analyzer.md
- **Developed by:** [Daniel Michałek](https://apify.com/danielmichalek) (community)
- **Categories:** SEO tools, Marketing
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$30.00 / 1,000 complete text coverage reports

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

## Keyword Coverage Gap Analyzer EN PL

### Overview

Compare phrase presence in supplied own and reference texts with English or Polish rules. The Actor returns referenceOnly, ownOnly and shared groups, document counts, original evidence and aliases for duplicate texts. Use the worksheet for a source review before content planning. Language and corpus ownership are user-declared.

### Input example

```json
{
  "language": "en",
  "ownPages": [
    {
      "id": "own-home",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own"
    },
    {
      "id": "own-copy",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own-copy"
    }
  ],
  "referencePages": [
    {
      "id": "ref-solar",
      "text": "Solar roof panels. Solar installation guide.",
      "sourceUrl": "https://example.com/reference"
    },
    {
      "id": "ref-price",
      "text": "Solar panels price. Installation price guide.",
      "sourceUrl": "https://example.com/reference-price"
    }
  ],
  "minNgram": 1,
  "maxNgram": 2,
  "minimumDocuments": 1,
  "maxPhrasesPerGroup": 50,
  "maxItems": 1
}
```

### Output

One complete report is one dataset item and GAP_REPORT JSON. GAP_CSV exports one row per returned phrase. REPORT contains a readable summary. The phrases array joins to groups by phraseIds. Counts refer to unique documents per corpus, with normalized text duplicates counted once and original alias IDs retained. suppliedOwnPages and suppliedReferencePages retain the input page counts.

NFC and lowercase normalize words, with a fixed EN/PL stopword list and no stemming. Eligible tokens start with a letter and contain 2 to 64 Unicode code points. Ngrams combine adjacent eligible tokens separated only by whitespace. Stopwords and punctuation break windows. Whitespace includes newlines and paragraph breaks, so this is a lexical comparison without sentence boundary analysis.

minimumDocuments applies to the larger of the two document frequencies. A shared phrase can therefore have fewer occurrences on one side. Groups use document frequency and stable phrase ordering. They retain eligibleCount, returnedCount and truncated. outputTruncated signals any limited group. Evidence preserves original UTF16 offsets and the first matching span from up to three documents per side. Every source ID remains available, and omitted evidence counts are explicit.

### Pricing and cost

The proposed price is USD 0.03 for one complete keyword-gap-report comparing up to 50 own and 50 reference supplied pages. Release receipts and the Store determine active pricing. No AI, content requests, proxy or external service is used by the comparison. Cloud cost and margin require separate acceptance.

### Limits and known scope

Input is limited to 2 MB, 50 pages per side, 20000 Unicode code points per text and 160 per ID. The index fails before delivery at more than 20000 distinct phrases. Output allows up to 200 phrases per group, with explicit truncation. A 45 second cooperative application budget is checked by the shared runner. It does not interrupt synchronous CPU work mid-function. Acceptance additionally applies a process timeout and records peak process memory.

Source URLs are identifiers. Queries and fragments are removed from output, and embedded credentials are rejected. Text coverage does not establish search volume, ranking difficulty, semantic equivalence, conversion opportunity or traffic growth. Inspect the original contexts and data rights before acting. Remove secrets and unnecessary personal data before upload. Storage retention follows your Apify settings.

### Development and recovery

Use Node 22 or newer. The dependency lock is reused from the accepted factory runtime. Install with npm ci --offline --ignore-scripts --omit=dev --no-audit --no-fund when cache is available, then run npm test and npm start. Normal start and test:local request a 128 MiB V8 old generation heap and an 8 MiB semi-space. Total process RSS is measured separately in acceptance. Docker uses npm start and installs the same lock with package scripts disabled. Shared libraries are vendored from \_factory/lib.

PREPARED precedes report effects. COMPLETE requires one physical report and the expected named charge count. Replay checks report, CSV, Markdown, runtime and pricing fingerprints, marker counters and physical dataset. Unknown or inconsistent effects require reconciliation before a retry. Preserve default storage and use a single writer for a job. A new intentional job uses fresh storage. CSV IDs are JSON strings inside quoted cells to preserve leading zeros.

# Actor input Schema

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

Select English or Polish explicitly. Ownership and language are user-declared.

## `ownPages` (type: `array`):

Supply 1 to 50 page objects. Exact normalized text duplicates count once, with aliases retained. Total input is limited to 2 MB.

## `referencePages` (type: `array`):

Supply 1 to 50 page objects. Exact normalized text duplicates count once, with aliases retained. Total input is limited to 2 MB.

## `minNgram` (type: `integer`):

From 1 to 3, at most maxNgram.

## `maxNgram` (type: `integer`):

From 1 to 3. Punctuation and stopwords break phrase windows. Whitespace includes line breaks.

## `minimumDocuments` (type: `integer`):

A phrase must occur in at least this many unique documents on one side. Duplicated text does not increase this count.

## `maxPhrasesPerGroup` (type: `integer`):

From 1 to 200 for each of referenceOnly, ownOnly and shared. Full eligible counts and truncation remain explicit.

## `maxItems` (type: `integer`):

One report includes both corpora and all returned phrase groups.

## Actor input object example

```json
{
  "language": "en",
  "ownPages": [
    {
      "id": "own-home",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own"
    },
    {
      "id": "own-copy",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own-copy"
    }
  ],
  "referencePages": [
    {
      "id": "ref-solar",
      "text": "Solar roof panels. Solar installation guide.",
      "sourceUrl": "https://example.com/reference"
    },
    {
      "id": "ref-price",
      "text": "Solar panels price. Installation price guide.",
      "sourceUrl": "https://example.com/reference-price"
    }
  ],
  "minNgram": 1,
  "maxNgram": 2,
  "minimumDocuments": 1,
  "maxPhrasesPerGroup": 50,
  "maxItems": 1
}
```

# Actor output Schema

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

No description

## `analysis` (type: `string`):

No description

## `csv` (type: `string`):

No description

## `report` (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 = {
    "language": "en",
    "ownPages": [
        {
            "id": "own-home",
            "text": "Steel roof panels. Roof repair guide.",
            "sourceUrl": "https://example.com/own"
        },
        {
            "id": "own-copy",
            "text": "Steel roof panels. Roof repair guide.",
            "sourceUrl": "https://example.com/own-copy"
        }
    ],
    "referencePages": [
        {
            "id": "ref-solar",
            "text": "Solar roof panels. Solar installation guide.",
            "sourceUrl": "https://example.com/reference"
        },
        {
            "id": "ref-price",
            "text": "Solar panels price. Installation price guide.",
            "sourceUrl": "https://example.com/reference-price"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("danielmichalek/keyword-gap-analyzer").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 = {
    "language": "en",
    "ownPages": [
        {
            "id": "own-home",
            "text": "Steel roof panels. Roof repair guide.",
            "sourceUrl": "https://example.com/own",
        },
        {
            "id": "own-copy",
            "text": "Steel roof panels. Roof repair guide.",
            "sourceUrl": "https://example.com/own-copy",
        },
    ],
    "referencePages": [
        {
            "id": "ref-solar",
            "text": "Solar roof panels. Solar installation guide.",
            "sourceUrl": "https://example.com/reference",
        },
        {
            "id": "ref-price",
            "text": "Solar panels price. Installation price guide.",
            "sourceUrl": "https://example.com/reference-price",
        },
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("danielmichalek/keyword-gap-analyzer").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 '{
  "language": "en",
  "ownPages": [
    {
      "id": "own-home",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own"
    },
    {
      "id": "own-copy",
      "text": "Steel roof panels. Roof repair guide.",
      "sourceUrl": "https://example.com/own-copy"
    }
  ],
  "referencePages": [
    {
      "id": "ref-solar",
      "text": "Solar roof panels. Solar installation guide.",
      "sourceUrl": "https://example.com/reference"
    },
    {
      "id": "ref-price",
      "text": "Solar panels price. Installation price guide.",
      "sourceUrl": "https://example.com/reference-price"
    }
  ]
}' |
apify call danielmichalek/keyword-gap-analyzer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,danielmichalek/keyword-gap-analyzer"
        }
    }
}
```

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/rkpfJWL145dc0xMvI/builds/4ixyOneaa2xd6OATS/openapi.json
