# Hreflang Cluster Receipts (`muazah/hreflang-cluster-receipts`) Actor

Check a known set of translated pages against your expected locale-to-URL matrix. Receipts for every page, edge and finding, with explicit 'could not verify' cases. No crawling, no JS rendering.

- **URL**: https://apify.com/muazah/hreflang-cluster-receipts.md
- **Developed by:** [muazah](https://apify.com/muazah) (community)
- **Categories:** SEO 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 Cluster Receipts

You know which URL should serve which locale. This checks what your pages actually declare against that matrix and gives you a receipt for every page, every edge and every finding.

### What it does

- Takes clusters you supply: an id plus an expected map of locale to public HTTPS URL (up to 100 distinct pages per run). Only those URLs are fetched. Nothing is crawled or discovered.
- Reads hreflang declarations from the HTML head and the HTTP `Link` header.
- Checks, per page: self-reference, every expected locale present and pointing at the expected URL, unexpected locales, duplicate locale labels with conflicting targets, HTML versus header conflicts, relative hrefs, canonical differences, redirects on supplied URLs, and x-default (advisory, or an error if you turn on Require x-default).
- Checks, per edge: if page A declares page B, whether B was fetched and declares A back. A missing return tag is reported only when B was fetched successfully.
- Cross-domain alternates are valid and are not flagged.

### What it does not do

- No JavaScript rendering. Declarations injected by scripts are not seen.
- No sitemap hreflang.
- No language detection, no automatic fixes, no changes to your site.
- A clean result is a structural result. It is not a statement that Google accepts or indexes these annotations.
- Locale codes are checked for the Google-supported shape (language, optional script, optional region; region alone is invalid) against the ISO-style code tables built into the runtime. This is a practical check, not a Google certification.
- Other tools check hreflang reciprocity, some at lower per-page prices. This one is built around your expected locale matrix and explicit "could not verify" cases. If you only need a quick reciprocity check, a cheaper tool may be enough.

### Honest unknowns

If a page fails to load, is blocked, is not HTML, or goes over the page cap, the cluster is `PARTIAL_UNVERIFIABLE` and edges touching it are `RECIPROCITY_UNVERIFIABLE`. A partial cluster is never graded as passing.

### Input

```json
{
  "clusters": [
    { "id": "product-a", "expected": { "en-US": "https://example.com/us/", "en-GB": "https://example.com/gb/", "de": "https://example.de/", "x-default": "https://example.com/" } }
  ],
  "requireXDefault": false,
  "ignoreUnexpectedLocales": false,
  "maxPages": 100
}
```

### Output

Rows in the default dataset: `cluster_receipt` (COMPLETE\_NO\_DECLARED\_ISSUES, COMPLETE\_WITH\_ISSUES or PARTIAL\_UNVERIFIABLE), `page_receipt`, `edge_receipt` and `finding`. Each has clusterId, sourceUrl, locale, expectedUrl, observedUrl, declarationMethod, reciprocalStatus, canonical, status, issueCode, evidencePointer, checkedAt, billablePage and coverage. Also `findings.csv`, `edges.csv`, `graph.json` and a run summary in the key-value store.

### Billing

Pay per successfully fetched distinct page. A page that appears in several clusters is billed once per run. Failed, blocked, invalid and over-cap pages are not charged. A successfully fetched page that declares nothing is charged and reported as missing declarations.

### Safety

HTTPS only, no private or loopback addresses (checked at every redirect), 3 redirects, 2 MB and 15 s per page, low concurrency.

# Actor input Schema

## `clusters` (type: `array`):

Each cluster: id and expected, a map of locale to the public HTTPS URL that should serve it. Include "x-default" in expected if you have a fallback page. Only these URLs are fetched; nothing is crawled.

## `requireXDefault` (type: `boolean`):

When on, a page with no x-default declaration is an error instead of an advisory.

## `ignoreUnexpectedLocales` (type: `boolean`):

When on, locales declared on a page but missing from your expected matrix are not reported. Use for sites that declare many more locales than you are checking.

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

Distinct URLs fetched per run across all clusters (hard cap 100). Pages over the cap are not fetched, not billed, and their cluster is PARTIAL\_UNVERIFIABLE.

## Actor input object example

```json
{
  "clusters": [
    {
      "id": "mozilla-home",
      "expected": {
        "en-US": "https://www.mozilla.org/en-US/",
        "de": "https://www.mozilla.org/de/",
        "fr": "https://www.mozilla.org/fr/"
      }
    }
  ],
  "requireXDefault": false,
  "ignoreUnexpectedLocales": true,
  "maxPages": 100
}
```

# Actor output Schema

## `receiptRows` (type: `string`):

Cluster, page, edge and finding rows.

## `summary` (type: `string`):

Per-cluster status and billing counts.

# 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 = {
    "clusters": [
        {
            "id": "mozilla-home",
            "expected": {
                "en-US": "https://www.mozilla.org/en-US/",
                "de": "https://www.mozilla.org/de/",
                "fr": "https://www.mozilla.org/fr/"
            }
        }
    ],
    "ignoreUnexpectedLocales": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("muazah/hreflang-cluster-receipts").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 = {
    "clusters": [{
            "id": "mozilla-home",
            "expected": {
                "en-US": "https://www.mozilla.org/en-US/",
                "de": "https://www.mozilla.org/de/",
                "fr": "https://www.mozilla.org/fr/",
            },
        }],
    "ignoreUnexpectedLocales": True,
}

# Run the Actor and wait for it to finish
run = client.actor("muazah/hreflang-cluster-receipts").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 '{
  "clusters": [
    {
      "id": "mozilla-home",
      "expected": {
        "en-US": "https://www.mozilla.org/en-US/",
        "de": "https://www.mozilla.org/de/",
        "fr": "https://www.mozilla.org/fr/"
      }
    }
  ],
  "ignoreUnexpectedLocales": true
}' |
apify call muazah/hreflang-cluster-receipts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,muazah/hreflang-cluster-receipts"
        }
    }
}
```

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/IgW3z72cwS1LRRRXB/builds/B0bDb3AP1eLyjDcbG/openapi.json
