# Internal Link Opportunity & Anchor Risk Auditor (`invincible_nova/internal-link-opportunity-anchor-risk-auditor`) Actor

Crawl one public website and return deterministic, review-ready internal-link suggestions with exact source sentences, anchor text, confidence components, and anchor-risk warnings.

- **URL**: https://apify.com/invincible\_nova/internal-link-opportunity-anchor-risk-auditor.md
- **Developed by:** [Sergei Pechenov](https://apify.com/invincible_nova) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 & Anchor Risk Auditor

A deterministic Apify Actor that crawls one public website and produces a human-review queue of internal-link opportunities. It does **not** call an LLM, require an SEO subscription, log in to a CMS, or publish links.

All documented analysis features use the same code path for free and paid Apify users. There is no account-tier feature gating. Cost is bounded with input limits; future monetization should use tiered event pricing instead of disabling promised behavior for free users.

### What makes the output reviewable

Each suggestion contains:

- source and target URL;
- the exact sentence already present on the source page;
- an exact phrase from that sentence proposed as the anchor;
- a 0–100 score and its numeric components;
- incoming-link count for the target;
- anchor repetition and collision warnings;
- target-specificity warnings when a match relies on a generic page or does not match the URL topic;
- a timestamp and explicit confidence label.

Suggestions are omitted when the source already links to the target. Orphan and underlinked pages are listed in the `OUTPUT` summary.

### Deterministic scoring

The same crawl produces the same ranking. The Actor:

1. extracts visible static main-content text, titles, H1s, descriptions and existing same-site links;
2. normalizes English and Russian terms with a small fixed stop-word list and light suffix normalization;
3. calculates corpus-aware term weights from target title, H1, description and URL slug;
4. searches source sentences for exact anchor spans containing target terms;
5. combines lexical relevance, title/H1 coverage, phrase quality, underlinked-target boost and optional priority-target boost;
6. subtracts a penalty for anchors already repeated across the site and emits separate risk flags.
7. requires a target-specific heading term and discounts matches that do not also support the target URL topic.

There is no hidden prompt, embedding model, random seed or third-party ranking API.

### Input

```json
{
  "startUrl": "https://example.com/sitemap.xml",
  "maxPages": 100,
  "maxSuggestionsPerPage": 3,
  "minimumScore": 48,
  "targetUrls": ["https://example.com/services/seo"],
  "excludePatterns": ["/account/", "/cart", "/tag/"],
  "respectRobotsTxt": true
}
```

`startUrl` can be a regular page, a sitemap, or a sitemap index. Crawling remains on the original hostname and accepts public HTTPS destinations only. `maxPages` is a hard limit on attempted HTML pages; supporting traffic is bounded to one `robots.txt` request and at most 20 sitemap documents.

With no supplied input, the Actor audits at most eight pages from Apify Academy. This bounded default exercises the complete free-user path and returns a non-empty review queue for Store health checks. A one-page site such as `https://example.com/` still succeeds with `status: "insufficient_pages"`, an empty review queue, and an explicit explanation in `OUTPUT`.

### Output example

```json
{
  "recordType": "opportunity",
  "sourceUrl": "https://example.com/blog/trail-guide",
  "targetUrl": "https://example.com/catalog/trail-shoes",
  "sourceSentence": "For wet forest routes, trail running shoes with deep grip improve control.",
  "suggestedAnchor": "trail running shoes with deep grip",
  "score": 76.4,
  "confidence": "high",
  "warningFlags": [],
  "targetIncomingInternalLinks": 1,
  "reason": "48 lexical-relevance points; 18 title/H1 coverage points; underlinked target boost +4"
}
```

### Guardrails and honest limitations

- `robots.txt` is respected by default.
- Private/reserved network addresses and credential-bearing URLs are blocked.
- Only static HTML is supported in v1. JS-only pages fail closed and are reported; content is not guessed.
- Lexical relevance can miss synonyms and meaning expressed with completely different words. Lower-confidence rows must be reviewed.
- No traffic or PageRank claim is made without first-party inputs such as a future GSC/crawl export.
- The Actor never modifies the website.
- A one-page or empty crawl returns `status: "insufficient_pages"`; at least two readable pages are required to calculate link opportunities.
- `maxPages` limits attempted HTML pages, including failures, so broken sites cannot create an unbounded request loop.

### Local development

```bash
npm ci
npm run verify
```

For a local Actor run, place input in `storage/key_value_stores/default/INPUT.json`, then run `npm start`.

### Free-user and ADWIC verification

Before every Store release, run the default input and compare its run status, dataset, and `OUTPUT` record with every claim above. Every promised feature must remain technically reachable by a free Apify user. Expensive workloads are controlled by input limits and tiered event pricing, not feature gates.

### Quality gate before Store publication

Build a human gold set on three permitted public sites (small, medium and content-heavy). Review the top ten suggestions per site. Continue only if at least 60% are accepted without changing the source/target pair, all rejected rows expose a useful warning or low score, and no suggestion duplicates an existing source-to-target link.

# Actor input Schema

## `startUrl` (type: `string`):

Public HTTPS website URL, sitemap.xml, or sitemap index on the website to audit. The bounded Apify Academy default exercises the full review-queue path.

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

Hard limit on attempted HTML page requests. robots.txt and up to 20 sitemap documents are bounded supporting requests.

## `maxSuggestionsPerPage` (type: `integer`):

Maximum review-queue suggestions emitted for each analyzed source page.

## `minimumScore` (type: `number`):

0-100 deterministic score threshold.

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

Optional same-site pages that should receive links when relevant.

## `excludePatterns` (type: `array`):

Case-insensitive substrings, for example /account/ or ?replytocom=.

## `respectRobotsTxt` (type: `boolean`):

Recommended. Disallowed paths are not requested.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout in seconds before the page is reported as failed.

## `maxPageBytes` (type: `integer`):

Maximum response body size accepted for one HTML page.

## Actor input object example

```json
{
  "startUrl": "https://docs.apify.com/academy/web-scraping-for-beginners",
  "maxPages": 8,
  "maxSuggestionsPerPage": 3,
  "minimumScore": 48,
  "excludePatterns": [
    "/account/",
    "/login",
    "/cart",
    "/checkout",
    "/tag/",
    "/author/"
  ],
  "respectRobotsTxt": true,
  "requestTimeoutSecs": 20,
  "maxPageBytes": 2000000
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

## `summary` (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("invincible_nova/internal-link-opportunity-anchor-risk-auditor").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("invincible_nova/internal-link-opportunity-anchor-risk-auditor").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 invincible_nova/internal-link-opportunity-anchor-risk-auditor --silent --output-dataset

```

## MCP server setup

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

```

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/xPRkAAGOfFV2aPLbd/builds/Z88u0dNHld9U6E7U9/openapi.json
