# Hreflang Checker: Bulk Pages, Return Links, x-default (`variegated_binturong/hreflang-checker-return-links`) Actor

Audit hreflang on many pages at once: invalid language and region codes, missing self-reference and x-default, alternates that do not link back, dead or redirecting alternates and canonical conflicts.

- **URL**: https://apify.com/variegated_binturong/hreflang-checker-return-links.md
- **Developed by:** [Luigi Fiano](https://apify.com/variegated_binturong) (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

$3.00 / 1,000 page checkeds

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

## Hreflang Checker: Bulk Pages, Return Links, x-default

Audit the hreflang tags of hundreds of pages in one run. For every page you get the tags it declares, whether it references itself, whether it has an x-default, and a plain list of problems. With return-link checking on, every alternate is opened too, so you see which language versions are dead, redirect, or forget to link back.

Hreflang only works when the tags are reciprocal and the codes are valid. One wrong code (`en-UK` instead of `en-GB`) or one missing return link and search engines can ignore the whole set.

### What it is for

- **International SEO audits**: check every language version of a site, not one page at a time.
- **Before and after a launch or migration**: confirm that new language folders all point at each other.
- **Monitoring**: schedule it weekly with `onlyProblems` and get only the pages that broke.

### What it checks

- Tags in the HTML `<head>` and in the HTTP `Link` header.
- Valid codes: ISO 639-1 language, optional script, optional ISO 3166 region or UN M.49 area (`es-419`), and `x-default`. Common mistakes are named: `en_us` (underscore), `en-UK` (use `en-GB`), `en-EU`, unknown languages.
- Self-referencing hreflang and x-default present.
- The same code pointing to two different URLs, missing href, relative URLs.
- Canonical pointing to another URL (hreflang on a non-canonical page is usually ignored).
- For each alternate: live (status), no redirect, links back to the page, and its own canonical is itself.
- Input URLs that redirect are reported.

### Input

| Field | What it does | Default |
|---|---|---|
| `urls` | One page URL per line | required |
| `checkReturnLinks` | Open every alternate and verify it links back | true |
| `maxAlternates` | Max alternates opened per page | 50 |
| `onlyProblems` | Output only pages with an issue | false |
| `concurrency`, `timeoutSecs`, `maxRetries` | Speed and resilience | 5, 20, 2 |

Example:

```json
{ "urls": ["https://www.example.com/en/", "https://www.example.com/it/"] }
```

Each URL is fetched once per run, even when many pages list it as an alternate.

### Output

One row per page:

```json
{
  "url": "https://www.example.com/en/",
  "finalUrl": "https://www.example.com/en/",
  "status": 200,
  "canonical": "https://www.example.com/en/",
  "hreflangCount": 3,
  "hasSelfReference": true,
  "hasXDefault": false,
  "alternates": [
    { "hreflang": "en", "href": "https://www.example.com/en/", "source": "html", "status": 200, "returnLink": true, "issue": null },
    { "hreflang": "it", "href": "https://www.example.com/it/", "source": "html", "status": 200, "returnLink": false, "issue": "no return link to this page" },
    { "hreflang": "en-UK", "href": "https://www.example.com/uk/", "source": "html", "status": 404, "returnLink": null, "issue": "status 404" }
  ],
  "ok": false,
  "issues": [
    "invalid hreflang \"en-UK\": region \"UK\" is not ISO 3166, use \"GB\"",
    "no x-default",
    "alternate it: no return link to this page",
    "alternate en-UK: status 404"
  ],
  "checkedAt": "2026-10-26T00:00:00.000Z"
}
```

A `SUMMARY` record in the key-value store counts pages checked, ok, with issues, without hreflang, with missing return links, with invalid codes and without x-default.

### Pricing

Pay per result: **$0.003 per page delivered** to the dataset ($3 per 1,000 pages), plus the small Apify platform usage of the run. Alternates opened for return-link checks are not charged as rows. With `onlyProblems` on, clean pages are not delivered and not charged.

### Notes

- It reads only the public HTML of the pages you provide and of the alternates they list. No login, no cookies, no personal data.
- Hreflang declared only in XML sitemaps is not read in this version; tags in the HTML head and the Link header are.
- JavaScript-injected tags are not executed: the check sees what a crawler sees in the raw HTML.

# Actor input Schema

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

One page URL per line. Each page is checked for its hreflang tags (HTML head and HTTP Link header), and every alternate it lists is opened to verify that it links back.

## `checkReturnLinks` (type: `boolean`):

Open every alternate URL and verify that it is live, does not redirect, and links back to the page. Turn off for a fast tags-only check.

## `maxAlternates` (type: `integer`):

Large international sites can list 100+ alternates per page. Only the first N are opened.

## `onlyProblems` (type: `boolean`):

Skip pages where everything is correct. You only pay for rows that are delivered.

## `concurrency` (type: `integer`):

How many pages are checked at the same time.

## `timeoutSecs` (type: `integer`):

Give up on a request after this many seconds.

## `maxRetries` (type: `integer`):

How many times a request is retried after a timeout, a 429 or a 5xx response.

## `maxUrls` (type: `integer`):

Safety cap on the number of input lines processed.

## Actor input object example

```json
{
  "urls": [
    "https://www.apple.com/uk/"
  ],
  "checkReturnLinks": true,
  "maxAlternates": 50,
  "onlyProblems": false,
  "concurrency": 5,
  "timeoutSecs": 20,
  "maxRetries": 2,
  "maxUrls": 5000
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `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 = {
    "urls": [
        "https://www.apple.com/uk/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("variegated_binturong/hreflang-checker-return-links").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 = { "urls": ["https://www.apple.com/uk/"] }

# Run the Actor and wait for it to finish
run = client.actor("variegated_binturong/hreflang-checker-return-links").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 '{
  "urls": [
    "https://www.apple.com/uk/"
  ]
}' |
apify call variegated_binturong/hreflang-checker-return-links --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,variegated_binturong/hreflang-checker-return-links"
        }
    }
}
```

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/B5PGyupoVi9YTZGLx/builds/ZadGZQ2OZ2iVJAbBb/openapi.json
