# Page Metadata & SEO Extractor (`cuantic_data/page-metadata-seo-extractor`) Actor

Bulk-extract and check page metadata for up to 500 URLs or an XML sitemap: title, meta description, canonical, robots, Open Graph, X Card, H1, hreflang and image alt text. One row per page with severity-rated issues, plus a run summary.

- **URL**: https://apify.com/cuantic_data/page-metadata-seo-extractor.md
- **Developed by:** [Cuantic Data](https://apify.com/cuantic_data) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.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

## Page Metadata & SEO Extractor - Title, Description, Canonical, Robots, H1, Hreflang

Extract and check the classic on-page SEO metadata of a whole list of pages in one run. Paste up to 500 URLs, or give an XML sitemap, and get one dataset row per page with its title, meta description, canonical, robots directives, Open Graph and X/Twitter Card basics, H1 headings, hreflang links and image alt-text counts, plus a list of **severity-rated issues** (title too long, missing description, canonical that does not match the page, noindex, images without alt, and more). A summary record totals the issues across the run.

Think of it as a **bulk meta tag checker**, **title and meta description length checker**, **canonical and hreflang audit** and **H1 / image alt checker** in one table, using deterministic checks you can read and reproduce. It uses plain HTTP and an HTML parser: no browser, small and fast.

> **What this is not.** The issues are static checks of the HTML each server returns. They do not predict rankings, indexing or how search results are displayed, and length thresholds are common guidelines rather than search-engine limits (see [Where the thresholds come from](#where-the-thresholds-come-from)).

### What it does

- Fetches each page once over plain HTTP(S) (no browser, no JavaScript) and reads the server-returned HTML.
- Extracts and checks: **title**, **meta description**, **canonical**, **robots** (meta tag and `X-Robots-Tag` header), **Open Graph** and **X/Twitter Card** (presence of the basics), **H1**, **hreflang** and **images without alt**. Choose which checks to run with the `checks` input.
- Reports issues as `{check, severity, code, message}` (`error`, `warning` or `info`) and counts them per page.
- Accepts a list of URLs **or** an XML sitemap (a sitemap index is followed one level deep). Only those URLs are fetched; links found on pages are never followed.
- Isolates failures: a page that times out, returns 404, is not HTML or points to a refused target gets a row with an `error` message, and the run continues.
- Writes a run summary to the key-value store record `OUTPUT`.

### How it relates to the other Cuantic Data SEO actors

- **This actor** covers classic page metadata: title, description, canonical, robots, H1, hreflang, image alt text, and *whether* Open Graph and X Card basics are present.
- **Bulk Rich Results Audit** (`bulk-rich-results-audit`) covers structured data: every JSON-LD block, `@type` values and parse errors, and the *detailed* Open Graph and X Card validation (relative or insecure image URLs, conflicting duplicate tags, invalid card types, fallbacks from Open Graph). This actor does not read JSON-LD at all. For Open Graph and X Card it checks only basic presence (a few missing-tag codes such as `og-missing`, `og-title-missing`, `og-image-missing` overlap in meaning with the sibling's), so running both on the same URLs gives mostly complementary results.
- **Bulk PageSpeed Audit** (`bulk-pagespeed-audit`) measures performance, accessibility and SEO scores with Lighthouse for the same kind of URL list.
- **Sitemap URL Extractor** (`sitemap-url-extractor`) is a handy first step to list the URLs of a site that you then feed to this actor.

### Who it is for

- **SEO audits and site migrations.** Snapshot titles, descriptions, canonicals and hreflang of every page before the switch, run again after, and compare the two datasets.
- **Metadata regression checks.** Run the same URL list after each release (for example on an Apify schedule) and watch the `OUTPUT` totals: a jump in `canonical-mismatch` or `robots-noindex` shows up in one number.
- **Content teams.** Find pages with missing or oversized titles and descriptions, missing H1s or images without alt text, in one sortable table.
- **Agencies.** Hand over a whole-site table instead of pasting URLs one by one into a single-page tool.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `urls` | array of strings | - | Pages to check (http/https). Deduplicated (order kept), max 500. |
| `sitemapUrl` | string | - | XML sitemap or sitemap index. Every `<loc>` page URL is checked, max 500. |
| `checks` | array of strings | all | Any of `title`, `description`, `canonical`, `robots`, `og`, `twitter`, `h1`, `hreflang`, `imagesAlt`. Empty means all. Fields of unselected checks are `null`. |
| `titleMinLength` / `titleMaxLength` | integer | `50` / `60` | Characters. Shorter gives an `info` issue, longer a `warning`. |
| `descriptionMinLength` / `descriptionMaxLength` | integer | `50` / `160` | Characters. Outside the range gives an `info` issue. |
| `sameOriginOnly` | boolean | `true` | For sitemaps: keep only child sitemaps and page URLs on the same origin as the sitemap URL. |
| `maxConcurrency` | integer | `10` | Pages fetched in parallel, 1 to 10. |
| `timeoutMs` | integer | `20000` | Timeout per request including redirects, 5000 to 60000 ms. |

At least one of `urls` or `sitemapUrl` is required. If both are given, `urls` is used, the sitemap is ignored and a warning is logged.

Example input:

```json
{
    "sitemapUrl": "https://www.example-shop.com/sitemap.xml",
    "checks": ["title", "description", "canonical", "robots", "h1", "imagesAlt"],
    "sameOriginOnly": true
}
```

### Output

One dataset item per URL:

```json
{
    "url": "https://www.example-shop.com/products/blue-widget",
    "finalUrl": "https://www.example-shop.com/products/blue-widget?ref=nav",
    "statusCode": 200,
    "redirectCount": 1,
    "contentType": "text/html; charset=utf-8",
    "checksRun": ["title", "description", "canonical", "robots", "og", "twitter", "h1", "hreflang", "imagesAlt"],
    "title": "Blue Widget - Handmade Garden Widgets and Accessories for Every Season | Example Shop",
    "titleLength": 85,
    "metaDescription": "A very blue widget.",
    "metaDescriptionLength": 20,
    "canonical": "https://www.example-shop.com/products/blue-widget",
    "canonicalMatchesUrl": false,
    "robots": { "meta": "index, follow", "xRobotsTag": null, "noindex": false, "nofollow": false },
    "openGraph": { "title": "Blue Widget", "description": null, "image": "https://www.example-shop.com/img/widget.jpg", "url": null, "type": "product", "siteName": "Example Shop" },
    "twitterCard": { "card": "summary_large_image", "title": null, "description": null, "image": null, "site": "@exampleshop" },
    "h1": { "count": 1, "texts": ["Blue Widget"] },
    "hreflang": [
        { "hreflang": "en", "href": "https://www.example-shop.com/products/blue-widget" },
        { "hreflang": "es", "href": "https://www.example-shop.com/es/products/blue-widget" }
    ],
    "images": { "total": 6, "missingAlt": 2, "emptyAlt": 1, "missingAltExamples": ["/img/a.jpg", "/img/b.jpg"] },
    "issues": [
        { "check": "title", "severity": "warning", "code": "title-too-long", "message": "Title is 85 characters (guideline: 60 or fewer); search results may truncate it." },
        { "check": "canonical", "severity": "warning", "code": "canonical-mismatch", "message": "Canonical (https://www.example-shop.com/products/blue-widget) differs from the page URL (https://www.example-shop.com/products/blue-widget?ref=nav). This is intentional for duplicate or parameterized URLs, but a mistake on an indexable page." },
        { "check": "imagesAlt", "severity": "warning", "code": "image-alt-missing", "message": "2 of 6 image(s) have no alt attribute." },
        { "check": "description", "severity": "info", "code": "description-too-short", "message": "Meta description is 20 characters (guideline: at least 50)." }
    ],
    "issueCounts": { "total": 4, "error": 0, "warning": 3, "info": 1 },
    "error": null
}
```

- `issues` holds every issue on the page, errors first. `issueCounts` totals them by severity.
- A field is `null` when its check was not selected, when the page does not have it (for example no `<title>`), or when the page could not be fetched. Check `checksRun` to tell the cases apart.
- `canonical` is resolved to an absolute URL (against `<base href>` if present). `canonicalMatchesUrl` compares it to the final URL after redirects, ignoring the fragment and a trailing slash; query strings count.
- `images.total` counts every `<img>`; `missingAlt` counts images with no `alt` attribute (images marked `role="presentation"`, `role="none"` or `aria-hidden="true"` are ignored); `emptyAlt` counts `alt=""`, which is the valid way to mark a decorative image and is not reported as an issue.
- `error` is `null` on success. On failure it holds the reason (for example `HTTP 404 for ...`, `Request timed out after 20000 ms`, `Not an HTML page (content-type: application/pdf)` or `Refused target: ...`) and all analysis fields are `null`.

Rows are written as each page finishes, so their order follows completion, not input. The `OUTPUT` record summarizes the run:

```json
{
    "total": 137,
    "audited": 134,
    "failed": 3,
    "skipped": 0,
    "chargeLimitReached": false,
    "source": "sitemap",
    "sitemapUrl": "https://www.example-shop.com/sitemap.xml",
    "checks": ["title", "description", "canonical", "robots", "og", "twitter", "h1", "hreflang", "imagesAlt"],
    "issues": { "total": 361, "error": 4, "warning": 120, "info": 237 },
    "byCode": [
        { "code": "description-too-short", "check": "description", "severity": "info", "pages": 71 },
        { "code": "title-too-long", "check": "title", "severity": "warning", "pages": 38 },
        { "code": "title-missing", "check": "title", "severity": "error", "pages": 4 }
    ],
    "pagesWithErrors": 4,
    "pagesWithoutIssues": 12,
    "averageTitleLength": 58.3,
    "averageDescriptionLength": 112.9,
    "failedUrls": [{ "url": "https://www.example-shop.com/old-page", "error": "HTTP 404 for https://www.example-shop.com/old-page" }],
    "skippedUrls": [],
    "note": "Deterministic static checks of the HTML returned by each server (no JavaScript rendering). Length thresholds are common SEO guidelines, not search-engine limits. JSON-LD and detailed Open Graph / X Card validation are not covered; see bulk-rich-results-audit."
}
```

`byCode[].pages` counts pages, not issues. In the summary, `audited` is the number of pages fetched and parsed successfully. There is no overall score: the counts are the result.

### Issue codes

Severities: `error` means a basic element is absent or broken; `warning` means it is likely to cause a problem; `info` is worth a look but can be intentional.

| Check | Code | Severity | Meaning |
|---|---|---|---|
| title | `title-missing` | error | No `<title>` element (a `<title>` inside inline SVG is ignored). |
| title | `title-empty` | warning | The `<title>` is empty. |
| title | `title-too-long` | warning | Longer than `titleMaxLength` (default 60 characters). |
| title | `title-too-short` | info | Shorter than `titleMinLength` (default 50 characters). |
| title | `title-multiple` | info | More than one `<title>`. |
| description | `description-missing` | warning | No non-empty `<meta name="description">`. |
| description | `description-too-long` / `description-too-short` | info | Outside `descriptionMinLength`-`descriptionMaxLength` (default 50-160 characters). |
| description | `description-multiple` | info | More than one meta description. |
| canonical | `canonical-missing` | info | No `<link rel="canonical">`. |
| canonical | `canonical-invalid` | error | The canonical is not a valid http(s) URL. |
| canonical | `canonical-relative` | info | The canonical is relative; an absolute URL is recommended. |
| canonical | `canonical-mismatch` | warning | The canonical differs from the page's final URL. Often intentional for parameterized or duplicate URLs. |
| canonical | `canonical-conflicting` | warning | Several canonicals with different targets. |
| robots | `robots-noindex` | warning | `noindex` or `none` in the robots/googlebot meta tag or the `X-Robots-Tag` header. |
| robots | `robots-nofollow` | info | `nofollow` or `none` in either place. |
| og | `og-missing` | warning | No `og:*` tags at all. |
| og | `og-title-missing` / `og-image-missing` | warning | `og:title` or `og:image` absent or empty. |
| og | `og-description-missing` | info | `og:description` absent or empty. |
| twitter | `twitter-card-missing` | info | No `twitter:card` tag. |
| h1 | `h1-missing` / `h1-empty` | warning | No `<h1>`, or only empty ones. |
| h1 | `h1-multiple` | info | More than one non-empty `<h1>`. Valid HTML and not a Google rule; some style guides prefer one. |
| hreflang | `hreflang-invalid-code` | warning | The value is not a language code (optionally with script or region) or `x-default`. |
| hreflang | `hreflang-relative-url` | warning | An alternate URL is relative; they must be fully qualified. |
| hreflang | `hreflang-conflicting` | warning | The same hreflang value points to more than one URL. |
| hreflang | `hreflang-missing-self` | warning | The page declares alternates but none points to itself. |
| imagesAlt | `image-alt-missing` | warning | One or more images have no `alt` attribute. |
| page | `response-truncated` | info | The HTML was larger than 2 MB; tags after the cut were not seen. |

### Where the thresholds come from

Google states that there is **no length limit** for titles or meta descriptions and that they are truncated "as needed, typically to fit the device width" ([title links](https://developers.google.com/search/docs/appearance/title-link), [snippets](https://developers.google.com/search/docs/appearance/snippet)). The length checks therefore use widely published SEO guidelines, are reported at modest severities, and can be changed with the input fields:

- **Title, 50-60 characters.** Moz, title tag guide: "Titles between 51-60 characters tend to result in the fewest rewrites by Google" and, in the FAQ, "If you keep your titles under 60 characters, our research suggests that you can expect about 90% of your titles to display correctly in the SERPs" ([Moz: title tag](https://moz.com/learn/seo/title-tag)). Ahrefs: "it's best practice to keep title tags between 50 and 60 characters to reduce the risk of truncation" ([Ahrefs: title tag SEO](https://ahrefs.com/blog/title-tag-seo/)). Longer than the maximum is a `warning`. The 50-character minimum is the lower end of that range; below it is only `info`.
- **Meta description, 50-160 characters.** Moz's meta description guide is not consistent with itself: the body says "The ideal length of a meta description is 120-160 characters" and "Descriptions longer than 160 characters risk being cut off in search results", while its FAQ says "Meta descriptions should be between 50-160 characters or around 680 pixels" ([Moz: meta description](https://moz.com/learn/seo/meta-description)). The 160-character maximum is supported by both statements. The 50-character minimum comes from the FAQ line only and is our own choice of the lenient end; if you prefer Moz's "ideal" range, set `descriptionMinLength` to 120. Both directions are reported as `info`.
- **Robots directives.** `noindex` and `none` are interpreted as defined in Google's [robots meta tag specification](https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag).
- **Hreflang.** Code format, fully-qualified URLs and "each language version must list itself as well as all other language versions" follow Google's [localized versions guide](https://developers.google.com/search/docs/specialty/international/localized-versions). The check does not verify that the other pages link back.
- **Image alt text.** WCAG technique H37: "When using the img element, specify a short text alternative with the alt attribute" ([H37](https://www.w3.org/WAI/WCAG22/Techniques/html/H37)); an `<img>` without `alt` is flagged. Separately, the W3C decorative images tutorial says "a null (empty) alt text should be provided ( alt="" ) so that they can be ignored by assistive technologies", which is why `alt=""` is not flagged ([W3C: decorative images](https://www.w3.org/WAI/tutorials/images/decorative/)).
- **Open Graph.** `og:title`, `og:type`, `og:image` and `og:url` are the required properties in [the Open Graph protocol](https://ogp.me/); this actor flags only the title and image, and `og:description` at `info`.

Third-party guideline pages (Moz, Ahrefs) can change; the defaults are editable for that reason. The quotes above were read on those pages on 2026-10-21.

### Safety

- Only the URLs you supply, or the `<loc>` entries of the sitemap you supply, are fetched. Links on pages are never followed. The robots.txt of the requested sites is not read; see [TERMS.md](./TERMS.md).
- Targets are checked before every request and every redirect: only `http` and `https`; no embedded credentials; no `localhost`, `.local`, `.internal` or single-label hostnames; no private, loopback, link-local, carrier-grade NAT, documentation, multicast or otherwise reserved IPv4/IPv6 addresses, including hostnames that resolve to one. The address is checked again when the connection is opened.
- Limits: 5 redirects, 2 MB per page and per sitemap file (after decompression), 20 child sitemaps and 10 MB of sitemap data per run, 500 URLs, the configured timeout, one retry for network errors, timeouts, 408, 429 and 5xx.
- Load on one host: with 500 URLs from the same site and the default concurrency of 10, the actor can send up to 10 simultaneous requests to that host and adds no delay between requests. Lower `maxConcurrency` for small or fragile sites.
- Requests identify as `Mozilla/5.0 (compatible; CuanticDataBot/1.0)`. No proxy is used, no cookies are sent and no login is attempted.

### Limitations

- **No JavaScript.** Only the HTML the server returns is checked. Tags injected by client-side scripts or tag managers are missed and may be reported as missing, and images added by scripts are not counted.
- **Guidelines, not rules.** Title and description length checks are conventions (see above); search engines may show different text than your tags.
- **Presence only for social tags.** Open Graph and X Card values are extracted and basic gaps are flagged. For URL syntax, conflicting duplicates, card types and fallback logic use Bulk Rich Results Audit.
- **No JSON-LD.** Structured data is not analyzed here.
- **Hreflang is one-sided.** Return links on the alternate pages are not fetched or verified.
- **Dynamic or personalized responses.** Pages that serve different HTML by user agent, location or cookies may differ from what visitors or crawlers see. Pages behind a login, bot protection or a consent wall usually return that wall instead.
- **Max 500 URLs per run.** Extra URLs are dropped with a warning. Split larger sites across several runs.
- **Sitemaps.** Plain XML only (no `.xml.gz` files); a sitemap index is followed one level deep.

### Pricing

Pay-per-event: one `page-checked` event per page that was fetched and parsed as HTML. Failed URLs (network errors, HTTP errors, non-HTML responses, refused targets) are not charged. See the Pricing tab for the current price. Apify platform usage is billed separately according to your plan.

If the maximum charge you set for a run is reached, the actor finishes the pages in progress, starts no new ones, and lists the URLs it did not start in `OUTPUT.skippedUrls`.

### Development

```bash
npm install
npm test          # unit tests (node:test, mocked network, no internet needed)
npm run smoke     # optional: one real request to https://example.com/ (or SMOKE_URL); prints SKIPPED without egress
```

### Support

Cuantic Data - cuanticwindows@gmail.com

Terms of use: see [TERMS.md](./TERMS.md).

# Actor input Schema

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

Pages to check (http or https). Only these pages are fetched; links on them are never followed. Duplicates are removed, order is kept, max 500 per run. If you also fill in a sitemap URL, this list wins and the sitemap is ignored.

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

An XML sitemap (or sitemap index, followed one level deep). Every <loc> page URL in it is checked, up to 500. Used only when the URLs list is empty. Gzipped sitemaps are not supported.

## `checks` (type: `array`):

Which groups of metadata to extract and check. Leave empty to run all of them. Fields of checks you do not select come back as null.

## `titleMinLength` (type: `integer`):

Titles shorter than this many characters get an informational issue. Default 50, the lower end of the 50-60 range recommended by Moz and Ahrefs.

## `titleMaxLength` (type: `integer`):

Titles longer than this many characters get a warning (search results may truncate them). Default 60. Google sets no limit; this is a common guideline.

## `descriptionMinLength` (type: `integer`):

Meta descriptions shorter than this many characters get an informational issue. Default 50 (Moz guideline of 50-160 characters).

## `descriptionMaxLength` (type: `integer`):

Meta descriptions longer than this many characters get an informational issue. Default 160 (Moz guideline).

## `sameOriginOnly` (type: `boolean`):

When reading a sitemap, keep only child sitemaps and page URLs on the same origin (scheme, host and port) as the sitemap URL. Protects against checking unrelated sites listed in a third-party sitemap.

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

Pages fetched in parallel. Keep it low for small sites to avoid putting load on them.

## `timeoutMs` (type: `integer`):

Maximum time for one page request, including redirects. A page that takes longer gets an error row and the run continues.

## Actor input object example

```json
{
  "urls": [
    "https://example.com/",
    "https://www.iana.org/help/example-domains"
  ],
  "checks": [
    "title",
    "description",
    "canonical",
    "robots",
    "og",
    "twitter",
    "h1",
    "hreflang",
    "imagesAlt"
  ],
  "titleMinLength": 50,
  "titleMaxLength": 60,
  "descriptionMinLength": 50,
  "descriptionMaxLength": 160,
  "sameOriginOnly": true,
  "maxConcurrency": 10,
  "timeoutMs": 20000
}
```

# 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 = {
    "urls": [
        "https://example.com/",
        "https://www.iana.org/help/example-domains"
    ],
    "checks": [
        "title",
        "description",
        "canonical",
        "robots",
        "og",
        "twitter",
        "h1",
        "hreflang",
        "imagesAlt"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cuantic_data/page-metadata-seo-extractor").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://example.com/",
        "https://www.iana.org/help/example-domains",
    ],
    "checks": [
        "title",
        "description",
        "canonical",
        "robots",
        "og",
        "twitter",
        "h1",
        "hreflang",
        "imagesAlt",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("cuantic_data/page-metadata-seo-extractor").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://example.com/",
    "https://www.iana.org/help/example-domains"
  ],
  "checks": [
    "title",
    "description",
    "canonical",
    "robots",
    "og",
    "twitter",
    "h1",
    "hreflang",
    "imagesAlt"
  ]
}' |
apify call cuantic_data/page-metadata-seo-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,cuantic_data/page-metadata-seo-extractor"
        }
    }
}
```

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/sHHqFHbd8NPhRKeZC/builds/wJVcDQktFGXLpShVl/openapi.json
