API Reference

OG Screenshot API

Generate a PNG screenshot of any URL using a saved template. The image is returned directly — drop the endpoint URL into any og:image meta tag and you're done.

Endpoint#

GET/api/og/v1/:templateId?url=<targetUrl>

Returns a PNG image of the target URL rendered with the template's configuration.

Authentication#

This endpoint requires no authentication header. The URL itself is the credential — it embeds your template ID, and Linkshot validates the request by checking:

  1. The templateId references an existing template owned by an active subscriber.
  2. The domain of the url query parameter is in your registered domain list (subdomains are allowed automatically).

Template IDs are public by design

Template IDs are meant to be embedded directly in your HTML — that's the whole point. The security model is domain-based: the template ID only works against URLs on domains you've registered, so someone who copies the ID from your markup can't use it to screenshot arbitrary sites. Treat it like a publishable key, not a secret.

Parameters#

ParameterTypeInDescription
templateId*
string (UUID)pathThe UUID of the template to use. Find this in Dashboard → Templates.
url*
string (URL)queryThe fully-qualified URL of the page to screenshot. Must be on a domain you have registered.

Template Configuration#

Each template stores a set of capture settings that control how the screenshot is taken. You configure these in the dashboard — they are not passed as query parameters.

SettingTypeDefaultDescription
viewportWidthinteger1200Browser viewport width in pixels (320–3840).
viewportHeightinteger630Browser viewport height in pixels (200–2160). 1200×630 is the OG standard.
deviceScaleFactorinteger2Pixel density multiplier (1–3). 2 produces retina-quality output.
scrollPositioninteger0Vertical scroll offset in pixels before capturing.
enableTailwindInjectionbooleantrueInject Linkshot's curated linkshot:-prefixed TailwindCSS utility set before capturing. Lets classes like linkshot:hidden or linkshot:text-6xl on your real markup apply only during the screenshot.
waitTimeoutinteger0Additional wait time in milliseconds after page load before capturing (0–3000).
blockPopupsbooleantrueBlock cookie banners and popup overlays before capturing.

Response#

A successful request returns the screenshot directly as a PNG image.

200 OKPNG image of the target page
Response Headers
Content-Type: image/png
Cache-Control: public, s-maxage=600, stale-while-revalidate=600
X-Cache: HIT | MISS

X-Cache header

X-Cache: HIT — the image was served from cache. A background job is regenerating it so the next request gets a fresh copy.

X-Cache: MISS — the image was freshly generated and is now cached.

Caching#

Screenshots are cached keyed by templateId + sha256(url).

  • Cache hit: The cached image is returned immediately. A background job regenerates it so the next request gets a fresh copy.
  • Cache miss: The screenshot is generated synchronously, stored to R2, and returned.
  • TTL: s-maxage=600, stale-while-revalidate=600 (10 minutes).

Example#

Requestbash
curl -X GET \
  "https://uselinkshot.com/api/og/v1/84ed4d78-7ad7-40cb-8abc-dbd122d4da79?url=https%3A%2F%2Fexample.com%2Fblog%2Fmy-post"
Usage in HTMLhtml
<meta property="og:image"
  content="https://uselinkshot.com/api/og/v1/TEMPLATE_ID?url=https://example.com/blog/my-post" />
<meta name="twitter:image"
  content="https://uselinkshot.com/api/og/v1/TEMPLATE_ID?url=https://example.com/blog/my-post" />
<meta name="twitter:card" content="summary_large_image" />

URL-encode the target URL

The url parameter must be URL-encoded (percent-encoded). Use encodeURIComponent() in JavaScript or urllib.parse.quote() in Python. Most OG image meta tag contexts encode automatically.

Error Responses#

All error responses have Content-Type: application/json and include an error field with a machine-readable code.

400 Bad RequestMissing url parameter
Response Bodyjson
{
  "error": "Missing url parameter",
  "message": "Please provide a url query parameter"
}
400 Bad RequestInvalid URL
Response Bodyjson
{
  "error": "Invalid URL",
  "message": "The provided url is not a valid URL"
}
404 Not FoundTemplate not found
Response Bodyjson
{
  "error": "Template not found",
  "message": "The specified template does not exist"
}
403 ForbiddenNo active subscription
Response Bodyjson
{
  "error": "No active subscription",
  "message": "The template owner does not have an active subscription"
}
403 ForbiddenDomain not authorized
Response Bodyjson
{
  "error": "Domain not authorized",
  "message": "The domain \"example.com\" is not in the authorized domain list"
}
500 Internal Server ErrorNavigation timeout — message includes the target URL
Response Bodyjson
{
  "error": "navigation_timeout",
  "message": "Page failed to load within timeout: https://example.com/page"
}
500 Internal Server ErrorScreenshot failed — message includes the error detail
Response Bodyjson
{
  "error": "screenshot_failed",
  "message": "Screenshot generation failed: <error detail>"
}