The text of a page is what people read. The metadata is what machines read about it: the title in the browser tab, the description a search engine shows, the image and headline that appear when someone shares the link. Most of the time you want the metadata, not the text, and it is a fraction of the cost to get.
This lesson builds a link preview service, the kind that turns a pasted URL into a card, and then covers the two jobs that sit next to it: listing the links on a page and finding out where a short link really goes.
What a page says about itself
Pages describe themselves in a few overlapping vocabularies:
- HTML basics. The
<title>element,<meta name="description">,<html lang>, the charset and viewport tags. - Open Graph.
og:title,og:description,og:image,og:site_name,og:typeand friends, created for Facebook shares and now read by nearly every chat and social app. - Twitter cards.
twitter:card,twitter:title,twitter:image, used by X and as a fallback by other apps. - Structured data. JSON-LD blocks describing products, articles, events, with prices and authors.
Metadata Extractor returns all of these in one flat object for 2 credits. The basics (title, description, lang, charset, author, keywords, robots, viewport, googlebot) are on every plan. Open Graph, Twitter, jsonld, favicons, canonical, headings and imgTags are premium fields, so design your code to work when they are missing.
curl -X POST "https://api.apiverve.com/v1/metadataextractor" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://github.com"}'requestUrl is what you sent; url is where the page ended up. Open Graph keys contain colons, so read them with bracket syntax.
Four things from a real response matter more than the field list.
Missing means empty string, not null. Calling it on https://example.com, a page with no description or Open Graph tags, returned "description": "", "og:title": "", "og:image": "" and so on. In JavaScript, data['og:title'] ?? data.title therefore returns the empty string and your card shows no title. Use || for fallbacks on this API, or normalise empty strings to null once at the boundary.
Keys have colons in them. data.og:title is a syntax error. Write data['og:title'] in JavaScript and data.get("og:title") in Python.
Sites add their own tags, and they come through. The GitHub response included dozens of keys that are not in the documented list, such as route-pattern, octolytics-url and apple-itunes-app, because the API returns every <meta> it finds. Pick the keys you need. Never spread the whole object into a database row or a response to your own clients, or you will store whatever any site on the internet decides to put in its head.
url is the final address. requestUrl is exactly what you sent; url is where the page landed after redirects (https://github.com became https://github.com/). Use url as the base for resolving relative paths and as the key you deduplicate on.
Your preview code is `const title = data['og:title'] ?? data.title;` and pages without Open Graph tags show a blank title. Why?
Answer the check above to continue.
Where does this short link go?
Short links hide their destination, which is fine for printed flyers and a problem for anything that makes decisions based on a URL: moderation, phishing checks, analytics deduplication, or a link preview that should show the real site. URL Unshortener follows the redirects and reports destination, destinationDomain, redirectCount and unshortened, with the full redirectChain on paid plans. It costs 10 credits.
curl -X POST "https://api.apiverve.com/v1/urlunshortener" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://bit.ly/3k3j4"}'Four hops from the short link to the final page. redirectChain lists each one.
That link took four redirects: the bit.ly hop, then an http to https upgrade, a path change, and another upgrade. The chain is useful evidence. A short link that bounces through an ad network or an unrelated domain before landing somewhere legitimate is worth flagging even when the final page is clean.
Sending a URL that does not redirect is safe. https://example.com came back with unshortened: false, redirectCount: 0 and destination equal to the input. That means you can run every URL through the unshortener without first guessing whether it is a short link, and branch on unshortened afterwards.
from urllib.parse import urlparse
from apiverve import apiverve # helper from lesson one
SHORTENERS = {"bit.ly", "t.co", "tinyurl.com", "goo.gl", "ow.ly", "buff.ly", "lnkd.in"}
def canonical_target(url: str) -> dict:
host = (urlparse(url).hostname or "").lower()
if host not in SHORTENERS:
return {"url": url, "domain": host, "hops": 0}
data = apiverve("urlunshortener", {"url": url})
return {
"url": data["destination"],
"domain": data["destinationDomain"],
"hops": data["redirectCount"],
}Checking a known-shortener list first is an optimisation, not a requirement: it saves 10 credits on the majority of URLs that are already direct. Drop the check if you need to catch every redirect, including a site's own /go/ links.
Two judgment calls before you ship this:
- Do not unshorten on behalf of someone who did not ask. Some short links carry per-recipient tracking, and following them registers a click. If you expand every link in incoming email for a security scan, you may be marking messages as "opened" for the sender. That trade-off is usually worth it for security tooling and rarely worth it for convenience features.
- Tracking parameters survive the trip. Destinations often arrive with
utm_*,fbclidorgclidattached. Strip them before you display or store the URL, both for privacy and so that the same page does not appear under twenty different addresses.
Next, the visual side of a page: screenshots and image inventories, and how to handle the temporary file links those APIs return, in Screenshots and images.