# Link Unfurl and Open Graph Preview Validator (`kingii98/og-unfurl-preview-validator`) Actor

Validate Open Graph and social-preview cards for public URLs across Slack, Discord, X, Facebook, LinkedIn, and iMessage without a browser.

- **URL**: https://apify.com/kingii98/og-unfurl-preview-validator.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Categories:** SEO tools, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-usage

## 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

## Link Unfurl and Open Graph Preview Validator

This Actor checks if a URL produces a correct link preview card. It fetches
each URL with the bot user agent of a chosen platform, reads the Open Graph
and Twitter Card meta tags, and checks the preview image. It gives a PASS
or FAIL verdict for each URL and platform pair.

Use this Actor as a release gate. Run it after every deploy. Run it again
on a schedule, because a working card can break after a CDN cache change
or a route change, with no code change to warn you.

### What it checks

For each URL and each selected platform, the Actor:

1. Fetches the page HTML with that platform's bot user agent.
2. Follows redirects, up to 5 hops. It rejects any hop that points to a
   private or reserved network address.
3. Reads the `og:title`, `og:description`, `og:image`, `og:type`, and
   `twitter:card` meta tags.
4. If `requireImage` is true, it fetches the `og:image` URL with a ranged
   GET. It reads only the image header, not the full file, to get the
   width and height.
5. Compares the image aspect ratio and minimum size against the platform's
   rule.
6. Writes one output record with a verdict, a reason code, and the exact
   failing rule.

Platform rules live in a versioned config file inside the Actor
(`src/og_unf/platform_rules.json`). Each rule lists its public source, or
states that the source is unofficial.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `urls` | array of string | `["https://example.com"]` | Public HTTP(S) URLs. Max 1000. |
| `platforms` | array of enum | all six | `slack`, `discord`, `x`, `facebook`, `linkedin`, `imessage`. |
| `requireImage` | boolean | `true` | Fail a check when `og:image` is missing or unreachable. |
| `expectedTitlePattern` | string (regex) | `""` (none) | Optional. `og:title` must match this pattern. |
| `followRedirects` | boolean | `true` | Follow redirects, up to 5 hops. |

### Output

One record per URL per platform, written to the default dataset:

- `url`, `platform`, `botUserAgentUsed`
- `httpStatus`, `redirectChain`
- `ogTitle`, `ogDescription`, `ogImage`, `ogType`, `twitterCardPresent`
- `missingTags`
- `imageStatus`, `imageContentType`, `imageBytes`, `imageWidth`,
  `imageHeight`, `aspectRatio`
- `verdict`: one of `PASS`, `NO_CARD`, `MISSING_TAG`, `IMAGE_UNREACHABLE`,
  `IMAGE_WRONG_RATIO`, `SPA_SHELL`
- `reasonCode` and `failingRule`: the exact rule that failed

A run summary goes to the default key-value store under the key
`SUMMARY`. It has pass and fail counts per platform, and a `gateResult`
of `PASS` or `FAIL` for use in continuous integration.

### Limits and safety

- HTTP(S) only. No browser rendering.
- The Actor rejects private and reserved URL targets (loopback, private
  ranges, link-local, multicast, and similar), including on redirect hops.
  This stops the Actor from being used to probe internal services.
- HTML body reads stop at 2 MB.
- Image reads use a ranged GET and stop at 64 KB, with a 300 KB fallback
  cap if a server ignores the Range header.
- Redirects stop at 5 hops.
- Requests use a 15-second timeout.
- At most 5 requests run at the same time.
- The Actor deduplicates `og:image` fetches by URL within a run.

### Local development

This project uses `uv`, `pytest`, and `ruff`.

```bash
uv sync
uv run pytest
uv run ruff check .
```

To run the Actor locally against the bundled fixture:

```bash
mkdir -p apify_storage/key_value_stores/default
cp .actor/default_input.json apify_storage/key_value_stores/default/INPUT.json
APIFY_LOCAL_STORAGE_DIR=apify_storage uv run python -m og_unf
```

Output records appear under `apify_storage/datasets/default/`. The
summary appears under `apify_storage/key_value_stores/default/SUMMARY`.

### Known limits

- Platform card rules change over time. Review
  `src/og_unf/platform_rules.json` on a schedule and after a reported
  preview failure.
- Apple does not publish an iMessage crawler spec. The `imessage` rule
  reuses the common Open Graph baseline with a wider tolerance and is
  marked unofficial in the config file.
- Some sites serve different HTML to known preview bots on purpose. The
  Actor reports what it received. It does not treat this as an error.

# Actor input Schema

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

Public HTTP(S) URLs to validate. Maximum 1000 per run. Private and reserved addresses are rejected.

## `platforms` (type: `array`):

Preview platforms to check. One price covers all selected platforms.

## `requireImage` (type: `boolean`):

Fail a URL/platform pair when og:image is missing or unreachable.

## `expectedTitlePattern` (type: `string`):

Optional regular expression. og:title must match it, or the check fails.

## `followRedirects` (type: `boolean`):

Follow HTTP redirects, up to 5 hops. Each hop is checked against the same public-target rule.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "platforms": [
    "slack",
    "discord",
    "x",
    "facebook",
    "linkedin",
    "imessage"
  ],
  "requireImage": true,
  "expectedTitlePattern": "",
  "followRedirects": true
}
```

# Actor output Schema

## `previewRecords` (type: `string`):

No description

## `runSummary` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/og-unfurl-preview-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/og-unfurl-preview-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 '{}' |
apify call kingii98/og-unfurl-preview-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/og-unfurl-preview-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/wnVzxZT9ez8zcrMoS/builds/1P1R9aHJod52fss08/openapi.json
