Skip to main content
RTILA Marketplace

Turn a list of URLs into a full link audit: outbound counts, schemes, redirects and page health

Audit page links in bulk: internal vs external counts, link schemes and navigation health for every URL.

SEO Beginner Stable v1.0.0 Updated Updated 2026-09-18 12 min read
Download
  • Windows x86_64

    BOT_outbound-link-checker.exe

    98.9 MB

    Download
  • macOS Apple Silicon chmod +x

    BOT_outbound-link-checker.sh

    87.5 MB

    Download
  • macOS Intel chmod +x

    BOT_outbound-link-checker.sh

    99.4 MB

    Download
  • Linux x86_64 chmod +x

    BOT_outbound-link-checker.sh

    122.5 MB

    Download

Do not rename the downloaded file - the filename carries the bot's configuration id.

This tab summarises what the bot does, what it costs, the technical requirements it places on your machine, and how the author is credited - the starting point before you download anything.

Key facts

What it does
Audit page links in bulk: internal vs external counts, link schemes and navigation health for every URL.
What it outputs
BOT_outbound-link-checker.exe
Difficulty
beginner
Platforms
windows, macos, macos_intel, linux
Estimated run time
1-10 minutes depending on URL count and page size
Internet at launch
Required
On this page

Overview

The Outbound Link Checker is an outbound link checker: a standalone desktop bot that reads every anchor on each URL you supply, classifies links by scheme and destination, counts internal vs external targets, records navigation health per page, and routes pages by link density - exporting four CSV datasets after the run.

You give it a list of URLs - one per line - and a link-density threshold. For every URL it opens a real browser page, waits for the document, and extracts four datasets: Outbound_Links (scheme-level anchor counts plus navigation diagnostics), Link_Census (a script-computed internal vs external aggregate with the top external domains), High_Link_Pages (pages above your threshold) and Good_Link_Partners (pages at or below it). Each dataset is de-duplicated by page URL, so re-running with overlapping lists never doubles rows.

Two design choices shape everything it reports. First, it blocks heavyweight resources - images, media, fonts, stylesheets, websockets, manifests, texttracks, eventsources and other assets - but deliberately does not block scripts. Pages load fast, yet client-rendered JavaScript still executes, so anchors injected by front-end frameworks are counted too. Second, it runs with a visible browser (headless is off in the configuration), which keeps the run observable and avoids the detection profile of headless traffic. Because everything runs on your desktop, your audit list - which properties, which competitors, which client sites - never passes through anyone else’s servers.

One honesty note up front: this is a page-structure auditor, not a crawler and not a broken-link checker. It never follows the links it counts, so it cannot tell you whether an outbound destination is alive. What it tells you is how each page is wired: how many links leave the site, how many stay inside it, which schemes are in play, which external domains receive the most anchors, and how the page itself behaved during navigation.

The technical SEO asks “which templates leak the most link equity off-site, and to whom?” and needs the external counts and top-domain lists per URL. The site owner mid-migration asks “did the new theme silently double my footer links?” and needs before-and-after Total_Links numbers on the same URL set. The outreach marketer asks “which pages on my site are sensible, uncluttered places to request a link back?” and needs the Good_Link_Partners dataset - pages whose link count stays under a sane ceiling. The agency auditor asks “can I hand the client a dated artifact instead of a screenshot of a dashboard?” and needs four CSVs with the run date attached by the filesystem. All four want numbers they produced themselves and can re-derive on demand, not a credit-metered report inside someone else’s cloud.

The run has no interactive prompts and no in-flow navigation commands. Before launch you fill two project-settings inputs derived from the configuration’s variables: urls_to_check, a required multi-line list (the shipped default holds three example URLs - replace them), and max_outbound_links, an optional threshold defaulting to 100. The launcher turns the list into the run queue; the engine navigates each URL itself, with up to five concurrent workers, and records the navigation outcome (status, final URL, redirect count, response time, content type, server, protocol, success flag, error text) as runtime variables.

Then, for every URL, four extract_data steps run in order. The first extracts Outbound_Links: the page title plus count-type properties over CSS selectors - css=a[href] for the total, css=a[href^='/'] for root-relative, css=a[href^='//'] for protocol-relative, css=a[href^='http'] for absolute HTTP(S), css=a[href^='#'] for fragments, and matching selectors for mailto, tel, javascript and href-less anchors (css=a:not([href])) - joined with the navigation variables via variable= selectors such as variable=last_http_status. The second extracts Link_Census: a nested list property over every css=a[href] whose raw rows (origin and href) pass through a script transformation that resolves, classifies and aggregates them into one object per page. The third and fourth extract_data steps re-extract the page-level totals under filter conditions - Number(item.Total_Links) > Number('${max_outbound_links}') for High_Link_Pages and Number(item.Total_Links) <= Number('${max_outbound_links}') for Good_Link_Partners - so every audited page lands in exactly one of the two routing datasets. When the queue is exhausted, each dataset is written to its own local CSV file named after the dataset.

What does the output look like?

The sample row below is the listing’s own published sample output, taken from the first dataset, so table and preview agree by construction:

Page_URLPage_TitleTotal_LinksRoot_Relative_LinksProtocol_RelativeAbsolute_HTTP_LinksFragment_LinksMailto_LinksTel_LinksJavascript_LinksNo_Href_AnchorsHTTP_StatusFinal_URLRedirect_CountResponse_Time_msNavigation_OK
https://rtila.com/RTILA X - Local-First AI Web Automation14896034122031200https://rtila.com/0412true

Page_URL is the URL you queued, and the de-duplication key plus required field for the dataset - a row always carries it.

Page_Title is the document title exactly as rendered, useful for spotting the moment a URL resolves to a sign-in or error page whose title gives it away.

Total_Links counts every anchor with an href, of any scheme. This is the number the threshold routing acts on.

Root_Relative_Links counts hrefs beginning with a single slash - links resolved against the current origin. A healthy internal-navigation page shows most of its density here or in absolute same-host links.

Protocol_Relative counts hrefs beginning with a double slash. This legacy pattern should normally be zero; a nonzero value is a small modernisation finding in itself.

Absolute_HTTP_Links counts fully qualified http and https hrefs. Note this includes absolute links pointing back at the same site - the internal vs external split lives in Link_Census, not here.

Fragment_Links counts in-page anchors beginning with a hash - table-of-contents and accordion markup typically.

Mailto_Links and Tel_Links count contact-scheme anchors. They are tallies only: no email address or phone number is copied into any output.

Javascript_Links counts hrefs invoking a javascript scheme. These are anchors that go nowhere without scripting, and a high count is an accessibility and crawlability finding worth recording.

No_Href_Anchors counts anchor elements with no href at all - decorative or JS-bound elements that are not links in any crawlable sense.

HTTP_Status, Final_URL, Redirect_Count, Response_Time_ms describe the navigation of the queued URL itself: what the server answered, where the browser settled, how many hops it took, and how long the response took in milliseconds.

Navigation_OK is the success flag for the page load; when it is false, Error (present in the dataset, empty on healthy rows) carries the engine’s failure text, and the link counts for that row should be treated as describing whatever partial document rendered, if any.

The configuration also captures Content_Type, Server and Protocol per row - the response’s MIME type, the server header and the negotiated protocol - so a page served as a PDF or from an unexpected host is visible in the data rather than silently counted.

One row per page, keyed by Page_URL, the census object reports: total anchors examined; internal and external counts after hostname resolution; unique_internal and unique_external (distinct targets, so a footer link repeated on every template is counted once); unique_external_domains (how many distinct off-site hosts receive links); top_external_domains (the ten most-linked hosts with their counts, e.g. a ranked “who does this page point at” list); fragment_links, mailto_links, tel_links, javascript_links and empty_href tallies; and href, which holds up to eight sample external destinations joined by a pipe so you can eyeball where the outbound weight goes without re-opening the page. The script also separates data and blob scheme targets and counts unparseable hrefs, so exotic markup is quarantined rather than misclassified as external.

What do the two routing datasets contain?

High_Link_Pages and Good_Link_Partners share a compact shape - URL (required, de-dup key), Total_Links, Absolute_HTTP, Root_Relative and HTTP_Status - and partition the audited set by the threshold. They are triage views: the first is your cleanup backlog, the second your safe-to-reference shortlist.

The census script resolves every href against the page’s own origin, then compares hostnames. A target counts as internal when its hostname equals the page hostname, including the www variant on either side - so www and bare-domain links to the same site are not falsely split. Everything else that resolves over http or https counts as external, and its hostname feeds the domain tally behind top_external_domains. Relative paths, root-relative paths and protocol-relative URLs resolve before comparison, so they classify correctly instead of falling through as “other”. Fragments, mailto, tel, javascript, data and blob targets never count as external; they are reported in their own columns. This is the same-site logic a careful human auditor would apply - implemented once, applied identically to every URL, and readable in the configuration’s transformation script rather than hidden in a vendor’s backend.

Yes, with the routing datasets doing the work. A link audit tool ultimately answers “which pages are link-dense, where does the outbound weight go, and which pages are clean enough to be worth referencing?” - and the four exports answer exactly that, dated by the run. Before requesting links to your own properties, check Good_Link_Partners for pages under your threshold; before fixing a template that bleeds equity, sort High_Link_Pages by Total_Links and read its top_external_domains. What it will not do is score domain authority, track rankings, or schedule itself: re-run the launcher on your own cadence and diff the CSVs to build the trend.

How does it compare to hosted crawlers and manual spreadsheets?

Copying hrefs into a spreadsheet by hand works for one page and collapses at twenty, and it produces no unique-domain or redirect data at all. This bot produces the full four-dataset audit on your own machine for nothing per run, and the workflow is yours to keep and re-run. Against subscription crawling services, the difference is structural: those meter pages or credits, hold your crawl data in their cloud, and define retention for you. Here the data lands on your disk immediately, the threshold is a plain number you control, and the marginal cost of a longer URL list is your own bandwidth. The trade is real - no crawl discovery, no historical dashboards, no destination status checks - but you own the artifact and the method outright, which is exactly what an auditor needs when a client asks “where did these numbers come from?”

How do you set it up on Windows, macOS and Linux?

Download the launcher for your operating system from this page. On Windows, run the executable; SmartScreen may ask for confirmation on first launch, and “More info” then “Run anyway” proceeds if you trust the published checksum. On macOS, Gatekeeper may quarantine the download; right-click and “Open”, or clear the flag with xattr -d com.apple.quarantine /path/to/OutboundLinkChecker, then launch. On Linux, the launcher is a shell script: chmod +x ./outbound-link-checker then ./outbound-link-checker. On first run, replace the three example URLs in the urls_to_check list with your own - one per line - and adjust max_outbound_links if 100 is not your threshold. A visible browser window opens during the run (headless is off by design); five workers run in parallel, so keep the list proportionate. To re-audit on a cadence, point your operating system’s scheduler at the launcher - the bot itself does not self-schedule.

What do I check when a run returns zero rows?

In order: did you leave the example URLs in place, or supply an empty urls_to_check list - the queue is only what you enter? For a specific URL missing from the output, check its row’s Navigation_OK and Error fields first: a failed navigation still records diagnostics rather than vanishing silently. If rows exist but High_Link_Pages is empty, that is a legitimate result - no page crossed the threshold; lower max_outbound_links and re-run to confirm. If Total_Links is zero on a page you know has links, the likeliest cause is a consent wall or interstitial that rendered instead of the document - compare Page_Title and Final_URL against what a human browser shows. The bot’s contract is that failure is loud: a page either navigates and reports its real structure, or reports its navigation error - counts are never fabricated to fill a row.

What are the bot’s limits?

It audits only the URLs you queue - there is no crawl, no sitemap discovery, no link following. It counts anchors present in the DOM after scripts run, including hidden ones such as off-canvas navigation, so counts reflect markup, not what a visitor visually sees. It does not report anchor text, rel attributes (nofollow, sponsored, ugc), link position, or destination status - an outbound link that 404s still counts as one outbound link. It cannot see logged-in or consent-walled content. Five workers is the configured ceiling for parallelism, and very large URL lists simply take proportionally longer. And it is a snapshotter, not a monitor: nothing re-runs or alerts by itself.

Point this bot at pages you own or have permission to test - your sites, your clients’ sites under engagement, public pages whose terms tolerate automated reading. Review robots.txt and each site’s terms before queueing third-party URLs, and keep request rates at what a patient human could sustain; five concurrent workers with assets blocked is already efficient, and a thousand-URL list pointed at one host is not an audit, it is a load test. The recorded Server, Protocol and response-time fields are for documenting the pages you are allowed to document, never for probing infrastructure you do not control. The output contains no page copy and no contact details - mailto and tel anchors are counted, not harvested - and it should stay that way in anything you publish from it. The acceptable-use policy applies in full before your first run.

#seo#link-audit#outbound-links#internal-links#web-audit#site-health

Sample output

The first rows a finished run produces. The Output tab documents every dataset and field.

Page_URLPage_TitleTotal_LinksRoot_Relative_LinksProtocol_RelativeAbsolute_HTTP_LinksFragment_LinksMailto_LinksTel_LinksJavascript_LinksNo_Href_AnchorsHTTP_StatusFinal_URLRedirect_CountResponse_Time_msContent_TypeServerProtocolNavigation_OKError
https://rtila.com/RTILA X - Local-First AI Web Automation14896034122031200https://rtila.com/0412true

Related resources

Technical details

Manifest problems for this listing:

  • datasets "High_Link_Pages" and "Good_Link_Partners" share 100% of their field names; consider merging them
Launcher version
1.0.0
Minimum runtime version
1.0.0
Internet required at launch
Yes
Update channel
live
Config encoding
plain

Requirements

  • Minimum launcher runtime version: 1.0.0.
  • Supported operating systems: Windows x86_64, macOS Apple Silicon, macOS Intel, Linux x86_64.
  • On macOS Apple Silicon and macOS Intel and Linux x86_64 the launcher is a shell script: if your system flags it as quarantined or strips the executable bit, grant execute permission with chmod +x BOT_outbound-link-checker.sh and approve the launcher in Gatekeeper (macOS security settings) before the first run.
  • First launch requires an internet connection to fetch the bot's configuration by id.

Responsible use

See our acceptable use policy and takedown process.

Commands this bot uses

These are the commands the published configuration actually uses.

The count is how many times each command appears in the published configuration; it is derived from the project, not hand-authored.

Frequently asked questions

How do I check outbound links on multiple pages at once?
Paste one URL per line into the urls_to_check list before the run - it ships pre-filled with three example URLs you should replace. The launcher builds the run queue from that list and processes it with up to five concurrent workers, so a few dozen URLs finish in minutes. Every URL produces one row in Outbound_Links and one row in Link_Census, de-duplicated by page URL.
What is the difference between internal and external links in an audit?
The census script resolves each href against the page origin. A link whose hostname equals the page hostname - including the www variant on either side - counts as internal; anything else resolving over http or https counts as external. Fragment, mailto, tel, javascript, data, blob and empty targets are tallied in their own columns and never counted as external.
How are mailto and tel links counted?
They are tallied, never copied. Mailto_Links and Tel_Links columns record how many such anchors exist on the page, and the Link_Census aggregate keeps matching counts. The external-samples field stores only http and https destinations, so no email address or phone number is written into any output file.
What does the max outbound links threshold do?
max_outbound_links (default 100) is the routing threshold applied after extraction. Pages whose Total_Links is strictly greater than the threshold land in the High_Link_Pages dataset; pages at or below it land in Good_Link_Partners. Lower the value to filter harder - for example, set it to 50 when you only want genuinely link-dense templates flagged.
How do I find pages with too many links?
Open the High_Link_Pages export and sort by Total_Links descending. Every row there exceeded your threshold, and the Absolute_HTTP and Root_Relative columns show whether the density comes from off-site links or from internal navigation markup. Empty High_Link_Pages with a populated Good_Link_Partners means no page crossed the threshold - a result, not a failure.
Does the checker follow links to other pages?
No. It audits exactly the URLs you supply and never crawls onward. It counts anchors on each page but does not visit their destinations, so it cannot tell you whether an outbound target is live, redirected or 404. It answers structure and page-health questions; destination status checking is a different job.
How do I export the link audit to CSV?
In standalone mode each dataset is saved as a local CSV file named after the dataset: Outbound_Links, Link_Census, High_Link_Pages and Good_Link_Partners. CSV, JSON and XLSX exports are all available from the launcher, and the files open directly in Excel or Sheets with no conversion step.
What navigation details does it record per URL?
Per URL the Outbound_Links row carries HTTP_Status, Final_URL, Redirect_Count, Response_Time_ms, Content_Type, Server, Protocol, Navigation_OK and Error - the same metadata a browser network panel shows, captured at run time. These values come from the engine's own navigation of your URL, not from any outbound link on the page.
Can it audit pages behind a login?
Not out of the box. Each run starts a fresh browser session with no stored credentials or cookies, so it sees what a logged-out visitor sees. If a URL redirects to a sign-in page, the audit records that final URL and its link structure - which usually looks obviously wrong in the census - rather than silently passing.
How are redirect chains reported?
Redirect_Count records how many hops occurred between the URL you requested and Final_URL, the address the browser settled on. A count of zero means the URL resolved directly; one usually means a normalisation hop such as adding www or upgrading to https; two or more suggests a chain worth flattening. Compare Page_URL against Final_URL to see exactly where the request ended.
Is counting links on a page allowed?
For pages you own or have permission to test, yes - this is a standard technical audit. For third-party pages, review their terms and robots.txt first, keep your URL lists proportionate, and remember five concurrent workers already load pages faster than a human browses. The bot collects only structural counts and public response metadata, no page content.
Does the bot need an internet connection to run?
Yes on first run: the launcher fetches its configuration by id from the backend at launch, so an internet connection is required at least the first time it starts.
Can I rename the downloaded file?
No. The downloaded file must keep its exact name - the filename carries the per-bot configuration id, and renaming it breaks the configuration fetch at launch.