HTTP API
The server answers on EMBISTO_ADDR (:8080); its OpenAPI spec is at /openapi.json, and /docs renders it with Scalar, at a pinned version loaded from jsDelivr.
The card
GET /v1/preview?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ answers:
{
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"canonical_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"type": "video",
"title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
"site_name": "YouTube",
"author": "Rick Astley",
"image": {"url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", "width": 1280, "height": 720},
"embed": {"html": "<iframe width=\"560\" height=\"315\" …></iframe>", "width": 560, "height": 315, "provider": "YouTube"},
"links": [
{"href": "https://www.youtube.com/embed/dQw4w9WgXcQ", "rel": ["player", "youtube", "opengraph", "twitter"], "type": "text/html", "…": "…"},
{"href": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", "rel": ["thumbnail", "opengraph", "jsonld", "twitter"],
"type": "image/jpeg", "media": {"width": 1280, "height": 720, "aspect_ratio": 1.7778}}
],
"sources": ["youtube", "oembed", "opengraph", "jsonld", "twitter", "html"]
}Empty fields are left out. icon, image and each of images are pictures with the fields listed under image.
| Field | Type | Meaning |
|---|---|---|
url |
string | The URL asked about. |
canonical_url |
string | The page's own address for itself, from its canonical link or Open Graph; else the URL that served it after redirects. |
type |
string | What the card shows: link, video, photo or rich. |
title |
string | The item's title. |
description |
string | The item's summary. |
site_name |
string | The site's name, such as YouTube. |
author |
string | Who made the item. |
author_url |
string | The author's page. |
keywords |
string | The page's keywords, separated by commas. |
published_at |
string | When the item was published, as RFC 3339. |
icon |
picture | The site's favicon or touch icon, preferring larger raster candidates. Without a declared icon, uses /favicon.ico on the final page's origin; this fallback URL is not a guarantee that a file exists. |
image |
picture | The card's picture: the first of images. |
images |
array of pictures | Every picture of the item the sources found, by their priority. |
image.alt |
string | Alternative text declared by the source (og:image:alt or twitter:image:alt). Omitted when absent; never generated from the title. |
image.url |
string | The picture's URL on its site. |
image.width, image.height |
integer | Its size in pixels, as the page states it or measured from the file's first bytes (PNG, JPEG, GIF, WebP, SVG, ICO); a measured JPEG is sized as its EXIF orientation shows it. |
image.variants |
array | The picture's thumbnails, when the server has them on; see thumbnails. |
image.variants[].preset |
string | The preset's name, such as thumb, card or large. |
image.variants[].url |
string | The thumbnail as JPEG, or PNG when it has transparency. |
image.variants[].webp_url |
string | The thumbnail as WebP, about half the bytes. |
image.variants[].width, image.variants[].height |
integer | The thumbnail's size, when the original's is known. |
video |
object | What the item plays. |
video.url |
string | A video file, or a player page. |
video.type |
string | Its media type; text/html for a player page. |
video.width, video.height |
integer | Its size in pixels. |
embed |
object | The provider's own way of showing the item inline. |
embed.html |
string | The HTML to insert. |
embed.width, embed.height |
integer | The size the HTML asks for. |
embed.provider |
string | Who offers it. |
duration |
integer | How long the video or audio lasts, in seconds. |
limited |
string | Why the card has little more than its URL: login_wall, consent_wall or blocked. Such a card carries the site's name and icon, never the wall's text. |
links |
array | Every resource the sources found, each URL once. |
links[].href |
string | The resource's URL. |
links[].rel |
array of strings | What the resource is (icon, thumbnail, image, player, app, file) and every source that found it. |
links[].type |
string | Its media type. |
links[].media |
object | Its size, when known. |
links[].media.width, links[].media.height |
integer | The size in pixels. |
links[].media.aspect_ratio |
number | Width over height, to four places. |
links[].html |
string | The HTML that embeds a player or an app. |
sources |
array of strings | The sources the fields came from, highest priority first. |
Routes
| Route | |
|---|---|
GET /v1/preview?url= |
The preview card. A URL without a scheme is taken as https. format=text gives plain lines instead: the title with its site, the description trimmed, and the URL. format=html gives a self-contained HTML card for an iframe or an email: inline CSS following light and dark, no JavaScript, and none of the provider's embed HTML. Templates add more formats; see templates. |
GET /iframely?url= |
The same preview in the format of Iframely's API, with links grouped by rel. |
GET /oembed?url= |
The same preview as an oEmbed answer, with the fields Iframely adds. |
GET /healthz |
The process is up. Its version is the release the server was built from; an image of main reports the commit as git describe names it, such as 0.2.0-3-g1a2b3c4, and a build that knows neither reports devel. |
GET /readyz |
The server takes new requests. It returns 503 while the server shuts down. |
GET /i/<signature>/<size>/<source> |
A thumbnail of a card's picture. The URLs come signed in the card's variants; see thumbnails. |
GET :9090/metrics |
Prometheus metrics, on a separate listener. |
GET :9090/debug?url= |
With EMBISTO_DEBUG=true: how a preview is made, on the internal listener only. |
DELETE :9090/v1/preview?url= |
Drops the URL's cached previews, on the internal listener only; see caching. |
embisto has no API keys and no limits per client: whatever serves it to clients decides who may call it and how often. It only refuses previews beyond EMBISTO_MAX_CONCURRENT_PREVIEWS being made at once, as busy. It protects itself from the pages it fetches; see hardening.
/iframely and /oembed let a client of Iframely switch by changing the address. They fail in Iframely's shape rather than as problem+json. Iframely compatibility lists where they differ from a local Iframely on a fixed set of URLs.
Errors
A failed request returns a non-2xx status with an RFC 9457 application/problem+json body. Its code field tells the failures apart:
code |
Status | Meaning |
|---|---|---|
invalid_request |
400 | A parameter is malformed or out of range, such as a negative size or a malformed language. |
invalid_url |
400 | The URL is missing, malformed, or not http/https. |
blocked_url |
403 | The URL resolves to a loopback, private, link-local or otherwise non-public address. |
fetch_failed |
502 | The page could not be fetched: the connection was refused, the status was an error, or there were too many redirects. |
timeout |
504 | The page did not answer in time. |
too_large |
502 | The page is larger than the body limit. |
not_found |
404 or 410 | The item does not exist or was removed: the page answered 404 or 410, or a provider says the video or post is gone. |
unknown_format |
400 | The server has no such format. |
busy |
503 | The server is making as many previews as EMBISTO_MAX_CONCURRENT_PREVIEWS allows; Retry-After says when to ask again. |
not_cached |
404 | A purge found nothing of the URL cached. |
cache_unavailable |
503 | The shared cache did not answer a purge. |
canceled |
503 | The client closed the request before the preview was ready. |
internal |
500 | embisto itself failed; the cause is logged, never answered. |
The codes of /i/ are in thumbnails. card.Catalogue() lists every code with its status and the words a client is shown; the code enum in /openapi.json is generated from it. A card.Error's text is always the catalogue's words or a detail written for clients: what went wrong underneath, which may name a resolved address or quote a page, is only logged.