# Web Metadata Contract Validator — OG, Twitter & Canonical (`damdamdoom/web-metadata-contract-validator`) Actor

Extract deterministic web metadata and machine-actionable validation findings from one static HTML page.

- **URL**: https://apify.com/damdamdoom/web-metadata-contract-validator.md
- **Developed by:** [Masatoshi Shimada](https://apify.com/damdamdoom) (community)
- **Categories:** 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 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Web Metadata Contract Validator — OG, Twitter & Canonical

Turn one public static HTML page into a deterministic JSON contract for metadata QA and automation. The Actor extracts document, canonical, robots, Open Graph, and Twitter Card metadata, then returns stable finding codes for missing, duplicate, invalid, or conflicting values.

Human Owner approved Public publication, Pay-per-event Monetization, and the production price on August 15, 2026. Live platform status is verified in the repository release report.

### What it returns

Each successful run writes exactly one item to the default dataset:

- requested and final URL, HTTP status, and fetch timestamp
- `title`, meta description, resolved canonical URL, and robots directives
- Open Graph title, description, image, and type
- Twitter Card type, title, description, and image
- machine-actionable findings with stable `code`, `severity`, and `field`
- all observed values under `evidence.metadataOccurrences`
- content type and parser version provenance

Absent values are `null`; the Actor does not copy a value from another field. When a scalar field has multiple definitions, its normalized value is also `null`, every occurrence remains in evidence, and an `*_MULTIPLE` finding is returned.

### Input

```json
{
  "url": "https://example.com/",
  "timeoutSeconds": 15,
  "maxResponseBytes": 5242880,
  "maxRedirects": 3
}
```

| Field | Required | Contract |
|---|---:|---|
| `url` | yes | One public `http` or `https` page |
| `timeoutSeconds` | no | Integer from 1 to 20; default 15 |
| `maxResponseBytes` | no | Integer from 1 to 5,242,880; default 5 MiB |
| `maxRedirects` | no | Integer from 0 to 3; default 3. Use 0 to reject the first redirect without following it |

### Output example

```json
{
  "requestedUrl": "https://example.com/",
  "finalUrl": "https://example.com/",
  "httpStatus": 200,
  "fetchedAt": "2026-08-07T00:00:00Z",
  "metadata": {
    "title": "Example Domain",
    "description": null,
    "canonicalUrl": null,
    "robots": [],
    "openGraph": {"title": null, "description": null, "image": null, "type": null},
    "twitterCard": {"card": null, "title": null, "description": null, "image": null}
  },
  "findings": [
    {
      "code": "META_DESCRIPTION_MISSING",
      "severity": "WARNING",
      "field": "metadata.description",
      "message": "Meta description is absent"
    }
  ],
  "evidence": {
    "metadataOccurrences": {
      "title": ["Example Domain"],
      "description": []
    }
  },
  "provenance": {"contentType": "text/html", "parserVersion": "0.1.0"}
}
```

The full occurrence map always contains every field defined in the dataset schema.

### Finding families

- missing fields: `TITLE_MISSING`, `META_DESCRIPTION_MISSING`, `CANONICAL_MISSING`, `OG_*_MISSING`, `TWITTER_*_MISSING`
- duplicate fields: corresponding `*_MULTIPLE` codes
- canonical checks: `CANONICAL_URL_INVALID`, `CANONICAL_ORIGIN_MISMATCH`
- consistency checks: `TITLE_OG_MISMATCH`, `TITLE_TWITTER_MISMATCH`, `DESCRIPTION_OG_MISMATCH`, `DESCRIPTION_TWITTER_MISMATCH`
- robots/canonical check: `NOINDEX_CANONICAL_CONFLICT`

Findings describe the metadata contract. They do not guarantee search rankings or social-platform behavior.

### Limits and safety

- Static HTML only; no browser and no JavaScript rendering.
- One URL and one connection at a time. `maxRedirects` bounds redirect following from 0 to 3; the default 3 preserves the existing behavior.
- DNS is validated on the initial URL and every redirect. Connections are pinned to the validated public IP, and local, private, link-local, reserved, multicast, and metadata-service destinations are rejected.
- User information in URLs, non-HTML responses, unsupported content encodings, HTTP errors, timeouts, and oversized responses fail explicitly.
- Both compressed and expanded response bodies are capped. HTML bodies are neither logged nor stored.
- No proxy, paid API, LLM, API key, or Secret is required.

Only fetch pages you are authorized to access and comply with applicable terms, robots guidance, privacy, copyright, and rate limits.

### Pricing

The approved production price is `$0.003` per successful default dataset item using Apify's built-in `apify-default-dataset-item` event. There is no Actor-start or custom paid event.

An approved private Free-plan cloud test observed `usageTotalUsd` of `$0.00010828961847888099` and `$0.0001060056708090835` for two successful 128 MiB runs. Both finished in under seven seconds with exactly one contract-valid dataset item. A third run rejected `127.0.0.1` before producing data, as required by the SSRF policy.

The normal-run average, `$0.000107147644643982245`, supports a positive conditional pro forma at this item price. It is not actual paid-user cost: the measurements came from private Free-plan runs and `usageTotalUsd` is an informational current-price estimate. Paid-user unit economics and paid demand remain unproven. See the repository cloud Cost Test report for the full assumptions and reconciliation.

### Local development

Python 3.11 or newer is required; the candidate image uses Python 3.14 and Apify SDK 4.0.0.

```powershell
py -3.14 -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
$env:PYTHONPATH = "."
.venv\Scripts\python.exe -m unittest discover -s tests -v
.venv\Scripts\ruff.exe check web_metadata_actor tests tools
```

Run with the Apify CLI and mock PPE logging:

```powershell
$env:VIRTUAL_ENV = (Resolve-Path .venv).Path
$env:Path = "$env:VIRTUAL_ENV\Scripts;$env:Path"
$env:APIFY_CLI_DISABLE_TELEMETRY = "1"
$env:ACTOR_TEST_PAY_PER_EVENT = "true"
npx --yes apify-cli@1.7.1 validate-schema
npx --yes apify-cli@1.7.1 run --purge --input '{"url":"https://example.com/"}'
```

Mock PPE logs use a local placeholder price and do not bill any account.

# Actor input Schema

## `url` (type: `string`):

A public HTTP or HTTPS page. JavaScript is not rendered.

## `timeoutSeconds` (type: `integer`):

Connection and response read timeout for each request.

## `maxResponseBytes` (type: `integer`):

Reject the response if either compressed or expanded bytes exceed this value.

## `maxRedirects` (type: `integer`):

Maximum redirect responses to follow. Use 0 to reject the first redirect without following it.

## Actor input object example

```json
{
  "url": "https://example.com/",
  "timeoutSeconds": 15,
  "maxResponseBytes": 5242880,
  "maxRedirects": 3
}
```

# Actor output Schema

## `results` (type: `string`):

Extracted metadata, all occurrences, and machine-actionable findings.

# 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 = {
    "url": "https://example.com/"
};

// Run the Actor and wait for it to finish
const run = await client.actor("damdamdoom/web-metadata-contract-validator").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 = { "url": "https://example.com/" }

# Run the Actor and wait for it to finish
run = client.actor("damdamdoom/web-metadata-contract-validator").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 '{
  "url": "https://example.com/"
}' |
apify call damdamdoom/web-metadata-contract-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,damdamdoom/web-metadata-contract-validator"
        }
    }
}

```

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/5g4ZTwIzohhoIm9Ja/builds/PVIuTT9PZ6bJWtr4x/openapi.json
