Every time someone pastes your URL into Slack, X, LinkedIn, WhatsApp, Discord, or Notion, those platforms fetch a handful of <meta> tags and render a card. When those tags are missing or malformed you get a bare blue link — and a fraction of the click-through. Open Graph metadata is the contract between your pages and every platform that will ever share them, which makes it one of the highest-leverage and most neglected parts of a technical SEO audit.
The hard part isn't fixing one page — it's auditing hundreds. Opening dev tools URL by URL doesn't scale, and a broken og:image on your most-shared article can go unnoticed for months. The OGSnap API from FetchAPI collapses the entire job into a single GET request: it fetches a page server-side, parses its Open Graph, Twitter Card, and JSON-LD tags, and returns normalized JSON you can assert against.
This guide shows you how to audit, fix, and monitor Open Graph metadata across an entire site — and how to use the same calls for competitor and structured-data intelligence.
Open Graph is a protocol (originally from Facebook, now universal) that lets a page describe itself to any consumer that isn't a human with a browser. The tags that matter most for SEO and sharing:
| Tag | Purpose | Common failure |
|---|---|---|
og:title |
Headline shown on the card | Missing or stale |
og:description |
Supporting copy under the title | Truncated or empty |
og:image |
The visual — the single biggest CTR driver | Missing, relative, or too small |
og:url |
Canonical URL of the object | Points to a redirect or the wrong page |
og:type |
website, article, product, … |
Wrong type breaks rich rendering |
og:site_name |
Brand name on the card | Missing |
Twitter Cards (twitter:card, twitter:title, twitter:image) are the X/Twitter mirror, and JSON-LD (application/ld+json) is the structured-data layer that Google actually consumes for rich results.
To be precise about the SEO claim: Google doesn't treat og: tags as a direct ranking factor. What they do control is how your content looks the moment it's shared, and click-through is what determines how far a link travels. JSON-LD, by contrast, is a genuine rich-results input. Audit both.
One request, no signup, no HTML parsing:
curl "https://fetchapi.tech/v1/ogsnap?url=https://github.com/NousResearch/hermes-agent"
{
"url": "https://github.com/NousResearch/hermes-agent",
"title": "GitHub - NousResearch/hermes-agent: The agent that grows with you",
"description": "The agent that grows with you. Contribute to NousResearch/hermes-agent development...",
"image": "https://opengraph.githubassets.com/.../NousResearch/hermes-agent",
"site_name": "GitHub",
"type": "object",
"og": { "title": "...", "description": "...", "image": "...", "image:width": "1200" },
"twitter": { "card": "summary_large_image", "title": "...", "image": "..." },
"jsonld": [],
"has_jsonld": false
}
Two things make OGSnap practical for auditing. First, the top-level fields (title, description, image, site_name, type) are already resolved: OGSnap falls back from og: to twitter: to the plain HTML <title>, and it resolves relative image paths against the final URL. Second, the raw og, twitter, and jsonld objects are included, so you can assert on exact tag values instead of trusting a summary. It also follows redirects, so url is the canonical destination you should be comparing against.
The real value shows up when you stop auditing one page and start auditing all of them. Here's a bash loop that flags every page missing a critical tag:
for url in \
https://fetchapi.tech \
https://fetchapi.tech/blog \
https://fetchapi.tech/docs; do
curl -s "https://fetchapi.tech/v1/ogsnap?url=$url" \
| jq -r --arg u "$url" '
[$u,
(if .title == "" then "MISSING_TITLE" else "ok" end),
(if .description == "" then "MISSING_DESC" else "ok" end),
(if .image == "" then "MISSING_IMAGE" else "ok" end),
(if .og["image:width"] == null then "NO_IMAGE_SIZE" else "ok" end)
] | @tsv'
done
Pipe that into CI and a missing og:image fails the build instead of quietly killing your share CTR.
For anything beyond a few dozen URLs, a small Python auditor is more robust — it can score each page, run concurrently, and only alert on regressions:
import requests
from concurrent.futures import ThreadPoolExecutor
CRITICAL = ["title", "description", "image"]
def audit(url: str) -> dict:
r = requests.get("https://fetchapi.tech/v1/ogsnap", params={"url": url}, timeout=20)
r.raise_for_status()
d = r.json()
if "error" in d:
return {"url": url, "score": 0, "issues": [d["error"]]}
issues = [f"missing og:{k}" for k in CRITICAL if not d.get(k)]
if d.get("og", {}).get("url") and d["og"]["url"] != d["url"]:
issues.append("og:url does not match canonical")
if not d.get("twitter", {}).get("card"):
issues.append("missing twitter:card")
score = max(0, 100 - 20 * len(issues))
return {"url": url, "score": score, "issues": issues, "title": d.get("title", "")}
with ThreadPoolExecutor(max_workers=8) as pool:
for row in pool.map(audit, MY_URLS):
flag = "OK " if row["score"] == 100 else "FIX"
print(f"{flag} {row['score']:>3} {row['url']} {row['issues']}")
Drop that into a weekly cron job and you get an early-warning system: you'll catch a broken og:image the day it ships, not the day a post goes viral.
og:image. The single biggest cause of ugly cards. Ship a 1200×630 PNG or JPG (1.91:1) and reference it with an absolute https:// URL. Relative paths technically work in some crawlers and silently break in others — don't rely on them.og:title / og:description. Copy-pasted from your template and never updated per page. Every page should override them with its own <h1> and meta description.og:url mismatches. If og:url points to a redirect or a www/non-www variant, platforms can split your share counts. It should equal the canonical URL — OGSnap returns exactly that in url, so compare the two.twitter:card (summary_large_image for content), plus twitter:title/description/image. OGSnap falls back to og: for you, but declaring them explicitly avoids ambiguity across platforms.has_jsonld and the jsonld array for headline, datePublished, and author — those feed rich results.The same endpoint is a lightweight competitive-research tool. Read how the leaders in your space describe themselves, then benchmark:
for url in https://stripe.com https://vercel.com https://supabase.com; do
curl -s "https://fetchapi.tech/v1/ogsnap?url=$url" \
| jq -r '[.site_name, .title, .image] | @tsv'
done
Because OGSnap stores nothing and costs one request, you can sweep a list of competitor article URLs and diff their tag strategy over time. Pair it with DiffCheck to alert when a competitor rewrites their homepage headline or swaps their social image — a quiet signal that they've repositioned.
jsonld fieldJSON-LD is where "social metadata" becomes real SEO. OGSnap returns parsed JSON-LD objects, so you can assert on the entity type rather than grepping raw HTML:
curl -s "https://fetchapi.tech/v1/ogsnap?url=https://vercel.com" \
| jq -r '.jsonld[]? | "@type=\(.["@type"]) name=\(.name // .headline // "-")"'
@type=Organization name=Vercel Inc.
@type=Service name=Vercel
@type=SoftwareApplication name=Vercel
For every article page, verify the JSON-LD exposes headline, datePublished, and author; for product pages, name, offers.price, and availability. Missing structured data is invisible in a browser but plainly visible to search engines.
Open Graph metadata is the most shareable part of your SEO surface, and OGSnap turns auditing it from an afternoon of dev-tools archaeology into one GET request per URL. Point it at your sitemap, score every page, wire the check into CI, and you'll never ship a broken social card again — while quietly gaining a competitive-intelligence and structured-data feed along the way.
Try it now:
curl "https://fetchapi.tech/v1/ogsnap?url=https://fetchapi.tech"
The free tier needs no signup. Explore the rest of the suite — the Markdownify API, the YouTube Transcript API, and DiffCheck — in the docs.