Count internal vs external links
Sort a page's links into internal and external: of five links, three on your own host and two elsewhere give a 0.6 to 0.4 split. Relative links count as yours, a subdomain counts as external, and every link shows its verdict. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Domain name
- https://example.com
- Domain name
- example.com
- Total links
- 5
- Internal links
- 3
- External links
- 2
This internal versus external link counter classifies every supplied link against your site's hostname, then returns the totals, ratios, and item-by-item result. It accepts the link list you already extracted from a page, including relative references such as /about or #details, as well as absolute URLs. Use it to review a page's link composition, verify an extraction pipeline, or turn a raw list into consistent metrics without fetching the page or sending a crawler across the site.
Prepare the site domain and page link list
Start with the hostname that defines your site and the link URLs collected from one page. The link list can mix absolute URLs, protocol-relative URLs, root-relative paths, path-relative references, query-only links, and fragment links. Relative references are resolved against the supplied site URL, so /services, team, ?
Each list item must be a non-empty string that can be parsed as a URL reference. The tool intentionally does not download HTML or discover links on its own; this keeps the calculation deterministic and lets you analyze lists produced by a crawler, browser export, CMS, test suite, or spreadsheet. Include every occurrence if you want page-level link frequency represented.
If the same destination appears three times, it is counted three times because the input describes three links. An empty list is rejected rather than returning a misleading zero-to-zero ratio.
Understand how classification and ratios work
Hostname comparison is case-insensitive because URL parsing normalizes hostnames. Paths, query strings, fragments, credentials, and ports do not change that hostname classification. A link to a different hostname is external, including a subdomain such as blog.example.com when the configured site is example.com.
For example, three internal links among four supplied links produce an internal ratio of 0.75. Ratios are rounded to six decimal places for stable machine-readable output, and together they sum to one. The links array preserves input order and shows the classification assigned to every original string, making disputed or unexpected counts easy to trace.
Use the result in an SEO review
The counts are best treated as a diagnostic signal, not a universal ranking target. Internal links help visitors and crawlers reach related pages, understand hierarchy, and move authority through a site. External links can support claims and guide readers to useful primary sources.
A page with an unusual balance deserves review, but the right balance depends on its purpose: a glossary hub, a product landing page, and a research article naturally link differently. Compare similar page templates, watch changes over time, and inspect the item-level classifications before drawing conclusions. In automation, run the counter after your extraction step and store the result beside the page URL and crawl timestamp.
You can flag pages with no internal links, detect templates that suddenly add many off-site destinations, or verify that a migration kept navigation links on the intended hostname. Because this capability performs no network request and uses no changing data, the same input always produces the same output.
What you can do with it
Review a landing page
Measure its internal and external link balance and inspect exactly how every extracted URL was classified.
Validate crawler output
Add deterministic counts to a link-extraction pipeline and catch unexpected off-domain destinations.
Check a site migration
Find page lists whose links still point at an old hostname after domains or environments change.
FAQ
What does it cost?
It is free to run in your browser on this page.
Are relative links internal?
Yes. Root-relative, path-relative, query-only, and fragment-only references resolve against the supplied site URL and are internal.
Are subdomains counted as internal?
No. Classification uses an exact hostname match, so blog.example.com is external when the configured hostname is example.com.
Does the tool fetch or crawl my page?
No. It only classifies the URL strings you provide and makes no network requests.
How are duplicate links counted?
Every list item is counted. Repeated URLs remain repeated because they represent separate link occurrences in the supplied data.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/seo/internal-link-count \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"domain":"https://example.com","urls":["/about","https://example.com/pricing","https://docs.example.net/guide"]}'const res = await fetch("https://api.kit.forhosting.com/seo/internal-link-count", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"domain": "https://example.com",
"urls": [
"/about",
"https://example.com/pricing",
"https://docs.example.net/guide"
]
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/seo/internal-link-count",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"domain": "https://example.com",
"urls": [
"/about",
"https://example.com/pricing",
"https://docs.example.net/guide"
]
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/seo/internal-link-count", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"domain":"https://example.com","urls":["/about","https://example.com/pricing","https://docs.example.net/guide"]}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"domain":"https://example.com","urls":["/about","https://example.com/pricing","https://docs.example.net/guide"]}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/seo/internal-link-count", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"domain": "https://example.com",
"urls": [
"/about",
"https://example.com/pricing",
"https://docs.example.net/guide"
]
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "seo.internal_link_count",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Prepare the site domain and page link list
The domain may be entered by itself, such as example.com, or as an absolute HTTP or HTTPS site URL. page=2, and #pricing are internal.
Understand how classification and ratios work
A link is internal when it resolves to an HTTP or HTTPS URL whose hostname exactly matches the supplied site's hostname. Non-web schemes such as mailto, tel, and ftp are also reported as external because they do not identify an HTTP or HTTPS page on the configured host. The result includes total_links, internal_links, and external_links, followed by internal_ratio and external_ratio as values from zero to one.
Use the result in an SEO review
Browser use is free, while API automation is charged at $0.002 per request.
Technical questions
What happens when the URL list is empty?
The request fails with an invalid_input error because a meaningful count and ratio require at least one link.