Skip to main content

Confirm

Are you sure?

Metadata API

Link previews and Open Graph from any URL

Everything a link preview needs, in one predictable shape — however the page chose to declare it.

By · Last updated: September 2026

TL;DR

POST a URL to /meta and get one JSON object: title, description, language, main_image_url, favicon_url, author_name, feed_url, publication_date and additional_author_information. Open Graph, Twitter cards, JSON-LD and plain HTML are reconciled by a fixed order of precedence, URLs come back absolute, and tags injected by JavaScript are read too. 5 credits per URL.

Free plan, no credit card. 1,000 credits a month.

Try it now — no signup

Try it on a page

Try your own URL

Free · no signup · 2 runs every 10 minutes

Paste up to 10 URLs

Each URL counts as one of your free runs; the ones past the limit are listed with a free API key to run them.

Your result will appear here.

Pick one of the pages above to get started.

The request

One POST, one bearer token

POST /meta
curl -X POST https://urlpipe.dev/meta \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog/launch", "sync": true}'
Response
{
  "title": "We're launching today",
  "description": "What we built, why, and what it costs.",
  "language": "en",
  "main_image_url": "https://example.com/images/launch-cover.jpg",
  "favicon_url": "https://example.com/favicon-96.png",
  "author_name": "Jane Doe",
  "feed_url": "https://example.com/feed.xml",
  "publication_date": "2026-09-01T08:00:00Z",
  "additional_author_information": {
    "twitter": "@janedoe"
  }
}

The response

The fields you get

Nine fields, always all present. Any of them is null when the page doesn't say — never a guess, never an invented URL.

FieldWhat it is
titleThe page's title, without the site name appended
descriptionThe page description
languageISO 639-1 code, e.g. en
main_image_urlAbsolute URL of the main image, or null (data: URIs are ignored)
favicon_urlAbsolute URL of the favicon, or null
author_nameThe author's name, if the page gives one
feed_urlAbsolute RSS or Atom feed URL, if discovered
publication_dateDate of first publication, ISO 8601 — not the last-modified date
additional_author_informationSocial handles, email or profile links for the author, if found

There is no canonical-URL field and no site-name field; if you need those, read them from the rendered HTML.

Reconciliation

When the page says it three different ways

Real pages declare the same thing in several places with different values — an og:title written for social, a <title> with the site name, an <h1>. /meta takes the first source in a fixed order that the page actually provides:

FieldOrder of precedence
titleog:title → twitter:title → <title> → <h1>
descriptionmeta description → og:description
language<html lang> → og:locale
main_image_urlog:image → twitter:image → JSON-LD image
favicon_urlrel=icon → rel=shortcut icon → rel=apple-touch-icon
author_namemeta author → JSON-LD author → a byline
feed_urlrel=alternate RSS/Atom → any other feed link
publication_dateJSON-LD datePublished → article:published_time → <time datetime>

Relative URLs are resolved against the page — a page that declares /favicon.ico comes back with a full URL you can use. A model reads the page's declarations to cope with the variety of markup in the wild, which is why /meta costs 5 credits rather than one; only the declarations go to it, not the whole page.

Rendering

Tags that only exist after JavaScript

Single-page apps often set their title and Open Graph tags from script — React Helmet, Vue Meta, Next.js client navigation. A metadata API that reads the raw HTML sees the app shell's defaults, or nothing. /meta reads the page after it has rendered in headless Chrome, so script-injected tags are there. Note that most social networks' own unfurlers don't run JavaScript, so if the tags only appear after rendering, your links may still preview badly on those sites — the Open Graph guide covers how to check.

Fallback

When there is no og:image

Plenty of pages have no preview image. Ask for a screenshot in the same visit with /scrape, and use it when main_image_url is null:

A preview image, always
import requests

res = requests.post(
    "https://urlpipe.dev/scrape",
    headers={"Authorization": "Bearer YOUR_API_KEY"},
    json={
        "url": url,
        "operations": ["meta", "screenshot"],
        "screenshot_options": {"full_page": False, "format": "webp"},
        "sync": True,
    },
)
ops = res.json()["operations"]
meta = ops["meta"]["result"]
image = meta["main_image_url"]  # or fall back to the screenshot's Base64 in ops["screenshot"]["result"]

The screenshot adds 1 credit. For a hosted link rather than Base64, call /screenshot on its own and read its X-Result-Url header: a no-key image URL, valid for 30 days, that goes straight into an <img>.

In practice

From response to preview card

A link preview needs four things: a title, a line of description, an image and a small icon. The response maps onto them directly, with sensible fallbacks when a field is null:

A preview card
const meta = await res.json()

const card = {
  title: meta.title ?? new URL(url).hostname,
  description: meta.description ?? "",
  image: meta.main_image_url,          // null → use a screenshot, see below
  icon: meta.favicon_url,
  byline: [meta.author_name, meta.publication_date?.slice(0, 10)].filter(Boolean).join(" · "),
}

Because every URL field comes back absolute, the card works wherever you render it — no need to remember the page URL and resolve /favicon.ico yourself. language tells you which way to set text direction or whether to translate, and feed_url lets a reader app offer to subscribe.

At scale

Unfurling at volume

Link previews are read far more often than pages change. Results are stored and reused for 7 days by default, up to 30 with max_age, and a reused result is free — so the second person pasting the same link costs nothing. A burst of identical requests while the first is still running waits for it, also free. Requests are async by default; send sync: true when the preview is on the critical path.

Limits

What it does not do

  • No oEmbed or embed HTML. It returns metadata, not a player or rich embed for YouTube or Twitter.
  • No custom fields. The nine fields are the shape; there is no schema to extend.
  • No canonical URL or site name in the response.
  • Public pages only. Private and intranet addresses are refused.

Pricing

What it costs

CallCreditsFree (calls/mo)Starter (calls/mo)Pro (calls/mo)Scale (calls/mo)
/meta5 credits2004,00011,00035,000

Cache hits and failed requests cost nothing. Paid plans are never cut off: past the allowance, extra credits are $1.50 per 1,000 credits. See every plan.

FAQ

Frequently asked questions

Which fields does the metadata API return?
title, description, language, main_image_url, favicon_url, author_name, feed_url, publication_date and additional_author_information. Every field is present; any can be null when the page doesn't declare it.
Does it read Open Graph tags added by JavaScript?
Yes. The page is rendered in headless Chrome before its metadata is read, so tags a single-page app sets from script are included.
What happens when og:title and the <title> disagree?
og:title wins, then twitter:title, then <title>, then the <h1>. Each field has a fixed order of precedence, so the answer is predictable.
Why does /meta cost more than /markdown?
A language model reads the page's declarations to normalize them, which costs money to run; /markdown and /screenshot use no model. /meta is 5 credits per URL, and a reused result is free.
What if the page has no og:image?
main_image_url comes back null. Request a screenshot in the same /scrape call and use it as the preview image instead.

Make your first request in five minutes.

Free plan, no card. Confirm your email and your API key is live — you'll be making real requests in minutes.