# Internal Link Audit: Orphan Pages & Sitemap Crawl (`cygnix_labs/internal-link-audit`) Actor

Find orphan pages, weak internal links, broken links and sitemap gaps. Sitemap crawl with inlink counts, anchors & fixes. First 25 free, then $0.002/page. No API keys.

- **URL**: https://apify.com/cygnix_labs/internal-link-audit.md
- **Developed by:** [Umair Saleem Mohammad Saleem](https://apify.com/cygnix_labs) (community)
- **Categories:** SEO tools, AI, Agents
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 page auditeds

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

## Internal Link Audit: Orphan Pages & Sitemap Crawl

**Answer first:** this actor is an **internal link audit and orphan page finder**. Give it a sitemap (or a URL list). It crawls those pages and labels each one **ORPHAN** (no internal links in), **WEAK** (few links in), **BROKEN_PAGE**, **HAS_BROKEN_LINKS** or **OK**, with the pages that link to it, top anchor texts and a fix. It also lists URLs that are linked but **missing from your sitemap**. **First 25 pages free every run, then $0.002 per page.**

| Quick facts | |
|---|---|
| Best for | Orphan page audits, internal linking audits, broken internal links, sitemap gaps, link depth / inlink counts |
| Input | A sitemap URL (indexes followed) or a list of URLs |
| Output | One row per page: `issue`, `status`, `inlinks`, `linkedFrom[]`, `topAnchors[]`, `brokenLinksOnPage[]`, `fix` |
| Price | First 25 pages free every run, then $0.002 per page. No start fee |
| Needs | No API keys, no proxies |
| Not included | JavaScript-injected links, external link checks |

### Why it matters

Orphan and weakly linked pages get crawled less, rank worse and are rarely cited by AI search. Internal linking is the cheapest SEO lever you control.

### Input example

```json
{
  "sitemapUrl": "https://crawlee.dev/sitemap.xml",
  "maxPages": 100,
  "weakThreshold": 2,
  "maxConcurrency": 5
}
```

Or pass a URL list instead of a sitemap:

```json
{
  "urls": ["https://example.com/", "https://example.com/about", "https://example.com/blog/post"],
  "maxPages": 50,
  "weakThreshold": 2,
  "maxConcurrency": 5
}
```

Defaults: `maxPages` 50, `weakThreshold` 2, `maxConcurrency` 5. Sitemap **indexes** are followed (nested sitemaps up to depth 2). Provide either `sitemapUrl` or `urls`.

### Issue types

| Issue | Meaning |
|---|---|
| ORPHAN | 0 inlinks from other crawled pages |
| WEAK | 1…`weakThreshold` inlinks (default threshold **2**) |
| BROKEN_PAGE | Page itself returned HTTP ≥400 (or failed) |
| HAS_BROKEN_LINKS | Page is fine but links to internal URLs that are ≥400 / failed |
| OK | Enough inlinks and no broken internal outlinks |

Priority order in the dataset: BROKEN_PAGE → ORPHAN → HAS_BROKEN_LINKS → WEAK → OK.

### Output (one row per page)

`url, issue, status, inlinks, outlinksInternal, linkedFrom[], topAnchors[], brokenLinksOnPage[], fix`

Sample:

```json
[
  {
    "url": "https://crawlee.dev/blog/archive",
    "issue": "ORPHAN",
    "status": 200,
    "inlinks": 0,
    "outlinksInternal": 36,
    "brokenLinksOnPage": [],
    "linkedFrom": [],
    "topAnchors": [],
    "fix": "Link to this page from 3+ related pages (hub/category + body links), or remove it from the sitemap if it should not rank."
  }
]
```

SUMMARY key-value: `pagesAudited`, `byIssue` counts, and `linkedButNotInList` (internal URLs linked from crawled pages but not in your sitemap/list, up to 200).

### Pricing (pay per event)

**First 25 pages in every run are free**, then **$2 per 1,000 pages** ($0.002 per page audited).

### What a run costs

| Pages audited | You pay |
|---|---|
| 25 | $0 (free in every run) |
| 100 | ≈ $0.15 |
| 500 | ≈ $0.95 |
| 1,000 | ≈ $1.95 |
| 5,000 | ≈ $9.95 |

(First 25 free each run.) Set a maximum charge per run in Apify run options; when the event spend cap is hit the actor stops cleanly (`eventChargeLimitReached`).

### Notes

- **Inlinks are counted only among pages crawled in the run.** For a full-site orphan check, raise Max pages to cover the whole sitemap.
- Pages are read as **server-rendered HTML** (Cheerio). Links injected only by client-side JavaScript (SPA routers) are not seen — the same limitation most search and AI crawlers have.
- Up to 100 linked-but-not-listed internal URLs are HEAD-checked for broken-link detection.

### How it compares (live Apify Store prices, verified 18 Oct 2026)

List prices on the Apify Free plan, read from the Apify Store API on 18 October 2026.

| Actor | Job focus | Price | Free pages | Cost for 100 pages |
|---|---|---|---|---|
| **cygnix_labs/internal-link-audit** (this) | Orphan + weak + broken internal + missing-from-sitemap, fix per page | **$0.002 / page** | **First 25 every run** | **$0.15** |
| dabooti/internal-link-graph-analyzer | Internal-link graph + orphan candidates | $0.001 / page + tiny start fee | No | ~$0.10 |
| plainrows/broken-link-checker | Broken links, redirect chains, sitemaps | $0.0015 / page | No | $0.15 |
| ib4ngz/internal-link-opportunity-finder | Contextual link opportunities (not an orphan audit) | $0.0065 / page | No | $0.65 |
| searchshark/domain-internal-link-crawler-screamingfrog-alternative | Full link crawl, one row per link | $0.001 / result + $0.01 start | No | depends on link count |
| logiover/website-link-graph-crawler | Link graph crawl | $0.0075 / item | No | depends on rows |
| nexgenwatch/internal-link-architecture-verifier | Architecture checks | $0.05 / page + $0.02 start | No | $5.02 |

**Honest take.** dabooti is cheaper per page ($0.001 vs $0.002) and bills every page. Here a 25-page client check costs $0 and 100 pages cost $0.15 vs about $0.10 there. Choose this one for free spot checks and for the orphan + broken + sitemap-gap labels with a fix per row; choose dabooti for very large crawls where only price matters.

### For AI agents / MCP

**When an agent should call this actor:** the user asks to find orphan pages, check internal linking, find broken internal links, or see which pages are missing from the sitemap.

Connect the Apify MCP server with only this actor enabled (works in Claude, ChatGPT, Cursor, VS Code, Codex and any MCP client):

```
https://mcp.apify.com?tools=cygnix_labs/internal-link-audit
```

Or search for it with the default server: `search-actors` keywords such as `internal links`, `orphan pages`, `broken links`.

Example `call-actor` input:

```json
{
  "sitemapUrl": "https://example.com/sitemap.xml",
  "maxPages": 200,
  "weakThreshold": 2,
  "maxConcurrency": 5
}
```

**What to read from the result:** rows are sorted BROKEN_PAGE → ORPHAN → HAS_BROKEN_LINKS → WEAK → OK; each has `url`, `issue`, `inlinks`, `linkedFrom`, `topAnchors`, `brokenLinksOnPage` and `fix`. `SUMMARY` has `byIssue` counts and `linkedButNotInList` (internal URLs missing from the sitemap).

**Cost control for agents:** Keep `maxPages` at 25 for a free first pass; raise it to cover the whole sitemap for a true orphan check. Pass `maxTotalChargeUsd` in `callOptions` to hard-cap spend. This actor is pay-per-event, so it also works with Apify agentic payments (no Apify account needed for the agent).

Example prompt: *"Audit internal links on example.com from its sitemap (200 pages) and list orphan pages and broken internal links with fixes."*

### FAQ

**Can I cap my spend?** Yes. Set a max charge per run; the actor stops when `eventChargeLimitReached` is returned and you are not billed beyond that cap.
**Do sitemap indexes work?** Yes. Nested `<sitemapindex>` files are followed up to depth 2, collecting `<loc>` URLs until Max pages is reached.
**Are nofollow links counted?** No. Anchors with `rel` containing `nofollow` are skipped for both outlink and inlink graphs.
**How polite is concurrency?** Default `maxConcurrency` is 5 (max 20). Keep it low on production sites so you do not hammer your own server.
**Does it need proxies or API keys?** No. It fetches your pages directly with a polite user-agent.
**JavaScript-only SPAs?** Only links present in the server HTML are audited. Pre-render or use a sitemap of real URLs if the site is client-rendered.

**Why does a page show as ORPHAN when my menu links to it?** Inlinks are counted only among pages crawled in the run. If the linking page was beyond `maxPages`, raise the cap. Links added only by JavaScript are not seen.

### Integrations

Run via the Apify API, schedule weekly runs, or send results to Google Sheets, Make, Zapier, n8n, Slack or webhooks.

### Changelog

- **0.1.6** (2026-10-18): README rewritten answer-first for search and AI agents: quick facts, comparison re-verified against 6 named actors on 18 Oct 2026, "For AI agents / MCP" section with example input. Price unchanged ($0.002/page, first 25 free).
- **0.1.2** (2026-10-16): Stronger orphan / internal-links / sitemap-crawl opening.

### More SEO and AEO tools by Cygnix Labs

- [Website SEO Audit + AEO/GEO Checker (full-site crawl)](https://apify.com/cygnix_labs/site-seo-aeo-audit)
- [AEO & GEO Audit + llms.txt Checker (URL list)](https://apify.com/cygnix_labs/aeo-readiness-checker)
- [Content Decay Audit (Search Console)](https://apify.com/cygnix_labs/content-decay-audit)
- [Keyword Cannibalization Checker (Search Console)](https://apify.com/cygnix_labs/keyword-cannibalization-finder)
- [Keyword Clustering + Content Briefs](https://apify.com/cygnix_labs/keyword-clusterer-brief)
- [Search Intent Classifier](https://apify.com/cygnix_labs/serp-intent-mapper)
- Free local SEO MCP server: [cygnix-seo-mcp](https://github.com/cygnixlabs-cmd/cygnix-seo-mcp) (`npx -y cygnix-seo-mcp`)

Questions or a feature request? Open an issue on this actor's Issues tab. We answer within 24 hours.

# Actor input Schema

## `sitemapUrl` (type: `string`):

sitemap.xml or sitemap index. All listed URLs are crawled (up to Max pages).

## `urls` (type: `array`):

Pages to audit if you don't have a sitemap.

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

Pages to crawl. You are charged per page crawled.

## `weakThreshold` (type: `integer`):

Pages with this many inlinks or fewer are flagged WEAK.

## `maxConcurrency` (type: `integer`):

Parallel requests. Keep low to be polite to your server.

## Actor input object example

```json
{
  "sitemapUrl": "https://crawlee.dev/sitemap.xml",
  "maxPages": 50,
  "weakThreshold": 2,
  "maxConcurrency": 5
}
```

# Actor output Schema

## `results` (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 = {
    "sitemapUrl": "https://crawlee.dev/sitemap.xml"
};

// Run the Actor and wait for it to finish
const run = await client.actor("cygnix_labs/internal-link-audit").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 = { "sitemapUrl": "https://crawlee.dev/sitemap.xml" }

# Run the Actor and wait for it to finish
run = client.actor("cygnix_labs/internal-link-audit").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 '{
  "sitemapUrl": "https://crawlee.dev/sitemap.xml"
}' |
apify call cygnix_labs/internal-link-audit --silent --output-dataset

```

## MCP server setup

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

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/EucdAxShIOXtqMiVK/builds/e2Lieex1iPk3GtINl/openapi.json
