# Internal Link Opportunity Finder (`junipr/internal-link-opportunity-finder`) Actor

Find internal link opportunities by matching target pages and topics against source-page text, headings, existing anchors, and site crawl data.

- **URL**: https://apify.com/junipr/internal-link-opportunity-finder.md
- **Developed by:** [junipr](https://apify.com/junipr) (community)
- **Categories:** SEO tools, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $11.50 / 1,000 url analyzeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Internal Link Opportunity Finder

Find source-backed internal linking opportunities by matching explicit target keywords and target URLs against visible source-page text. The Actor checks headings, sentence context, and existing anchors before emitting a recommendation. Each row tells an editor where the keyword appeared, whether the exact target link already exists, and why the opportunity was selected.

Use it for content-hub planning, internal-link implementation sheets, refresh workflows, and link coverage reviews. The Actor does not invent semantic relationships: a configured keyword must be visible on an accepted source page, and every target requires an explicit URL.

### How matching works

Provide `targetKeywords` and `targetUrls` in matching order. A keyword may also be an object containing `keyword` and `targetUrl`. For each accepted source page, the Actor:

1. extracts visible text, headings, and anchor destinations;
2. resolves target URLs against the source URL;
3. checks whether an exact target link is already present;
4. counts visible keyword occurrences;
5. captures bounded sentence context and a section hint;
6. returns an opportunity only when the source evidence supports it.

`excludeExistingLinks` removes targets that already have an exact link. Disable it when reviewing existing anchors and placement quality. `anchorSuggestionMode` can preserve the exact configured phrase or use a matching heading when one is present. Suggestions remain editorial inputs, not automatic link changes.

### Input

| Field | Purpose | Default / cap |
| --- | --- | --- |
| `sourceUrls` | Pages on which links may be added or reviewed | empty |
| `targetUrls` | Destination URLs paired with keywords | empty |
| `targetKeywords` | Visible phrases or `{ keyword, targetUrl }` records | empty |
| `maxSourcePages` | Maximum source pages analyzed | 10 default, 25 maximum |
| `maxOpportunities` | Maximum emitted opportunity rows | 50 default, 250 maximum |
| `excludeExistingLinks` | Remove exact target links already present | `true` |
| `anchorSuggestionMode` | `exact`, `standard`, or `contextual` | `standard` |
| `htmlInputs` | Captured source records with `sourceUrl` and HTML | empty |
| `allowedDomains` | Optional hostname allowlist for source retrieval | empty |
| `maxPages` | Maximum pages retrieved | 1 default, 25 maximum |
| `includeEvidence` | Preserve evidence excerpts in rows | `true` |
| `includeRawSnapshots` | Store accepted raw source HTML | `false` |
| `maxChargeUsd` | Stop before the next event exceeds this amount | 35 USD maximum |

Example:

```json
{
  "sourceUrls": [
    "https://publisher.example/guides/reliable-workers"
  ],
  "targetUrls": [
    "https://publisher.example/guides/queue-retries",
    "https://publisher.example/guides/monitoring"
  ],
  "targetKeywords": [
    "queue retries",
    "operational monitoring"
  ],
  "excludeExistingLinks": true,
  "anchorSuggestionMode": "standard",
  "maxOpportunities": 25,
  "allowedDomains": ["publisher.example"],
  "maxChargeUsd": 5
}
```

The Store default has no sources or target mappings and returns a nonempty `source_required` diagnostic. Missing keyword-to-URL mappings return `targets_required`, and pages with no supported match return a clear no-opportunity diagnostic.

### Dataset output

Every row preserves provenance and the opportunity decision:

- `sourceUrl` and `pageTitle` identify the source page;
- `targetUrl` and `targetKeyword` identify the configured destination pair;
- `suggestedAnchor` preserves the exact phrase or matching heading;
- `sourceContext` quotes bounded visible context around the keyword;
- `existingLinkFound` records exact target-link presence;
- `opportunityScore` combines occurrence, heading, missing-link, and context signals;
- `reasonCode` explains the decision path;
- `linkPlacementHint` identifies a matching section or quoted context;
- `issueCode`, `evidenceSnippet`, and `recommendation` support review.

The score ranks supported matches; it does not prove that adding a link improves rankings or reader experience. Editors should validate intent, destination quality, anchor naturalness, and navigation value.

### Reports

When `includeReport` is enabled, key-value storage receives a Markdown summary, CSV records, issue JSON, bounded evidence excerpts, and run metadata. Raw accepted source HTML is stored only when explicitly requested.

### Pay-per-event pricing

| Event | Price (USD) | When charged |
| --- | ---: | --- |
| `actor-start` | 0.0350 | after successful startup |
| `url-analyzed` | 0.0115 | once for each accepted source URL |
| `link-signal-extracted` | 0.0115 | before a supported link signal is recorded |
| `internal-link-opportunity-generated` | 0.0115 | before each paid opportunity row |
| `executive-report-generated` | 0.4000 | before report artifacts are stored |

Apify platform usage pass-through is off. Higher-tier Store discounts are reserved in the pricing policy. The run checks `maxChargeUsd` before each event; rejected, partial, or over-budget charges stop the related output write.

### Safety and interpretation

Only HTTP and HTTPS source pages are retrieved. Loopback and private IPv4 targets are rejected, optional allowlists restrict accepted source hosts, and page counts, opportunity counts, response size, and timeouts remain bounded. Discovered links are inspected but not crawled. Access-denied and human-verification pages become diagnostics.

The Actor reads visible source content and exact anchors only. It does not edit pages, follow authenticated links, determine search-engine policy compliance, or promise ranking effects. Review every recommendation in the context of the source article and destination page before implementation.

# Actor input Schema

## `sourceUrls` (type: `array`):

Public source URLs to collect evidence from.

## `targetUrls` (type: `array`):

Same-index target for each target keyword.

## `targetKeywords` (type: `array`):

Strings or objects with keyword and targetUrl.

## `maxSourcePages` (type: `integer`):

Maximum source pages to inspect.

## `maxOpportunities` (type: `integer`):

Maximum internal-link opportunities to emit.

## `excludeExistingLinks` (type: `boolean`):

Exclude opportunities where the source page already links to the target.

## `anchorSuggestionMode` (type: `string`):

Controls how suggested anchor text is selected.

## `startUrls` (type: `array`):

Optional public page URLs to analyze when actor-specific URL fields are not used.

## `sitemapUrls` (type: `array`):

Optional public XML sitemap URLs. Accepted pages remain bounded by maxPages.

## `htmlInputs` (type: `array`):

Optional captured HTML records with sourceUrl and html or currentHtml for deterministic analysis.

## `allowedDomains` (type: `array`):

Optional hostname allowlist for fetched pages.

## `maxPages` (type: `integer`):

Maximum pages to fetch in one run.

## `maxDepth` (type: `integer`):

General link discovery is disabled.

## `includeEvidence` (type: `boolean`):

Include short source evidence snippets in output rows.

## `includeRawSnapshots` (type: `boolean`):

Opt in to raw HTML artifacts in key-value storage.

## `requestDelayMs` (type: `integer`):

Delay in milliseconds between outbound page requests.

## `timeoutMs` (type: `integer`):

Maximum time in milliseconds to wait for a page request.

## `userAgentMode` (type: `string`):

User agent profile to use for public page requests.

## `maxChargeUsd` (type: `number`):

Maximum estimated PPE spend before the actor exits gracefully.

## `includeReport` (type: `boolean`):

Write the markdown summary report to the run key-value store.

## Actor input object example

```json
{
  "sourceUrls": [],
  "targetUrls": [],
  "targetKeywords": [],
  "maxSourcePages": 10,
  "maxOpportunities": 50,
  "excludeExistingLinks": true,
  "anchorSuggestionMode": "standard",
  "startUrls": [],
  "sitemapUrls": [],
  "htmlInputs": [],
  "allowedDomains": [],
  "maxPages": 1,
  "maxDepth": 0,
  "includeEvidence": true,
  "includeRawSnapshots": false,
  "requestDelayMs": 250,
  "timeoutMs": 15000,
  "userAgentMode": "standard",
  "maxChargeUsd": 35,
  "includeReport": true
}
```

# Actor output Schema

## `results` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("junipr/internal-link-opportunity-finder").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("junipr/internal-link-opportunity-finder").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 '{}' |
apify call junipr/internal-link-opportunity-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,junipr/internal-link-opportunity-finder"
        }
    }
}

```

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/DnEqaaZaGdcxiwQ7x/builds/aWP0OPfhEAn2Ni2Yp/openapi.json
