GUIDE

Open Graph Image API: Request Shape, Caching, and Versioning

A reference-level look at how a well-built open graph image API should be shaped — request parameters, response format, cache headers, and the versioning model that keeps old shared links stable.

August 16, 20266 min read

Most explanations of an open graph image API stop at "you send data, you get an image back." That's true but incomplete — the details that actually determine whether an API is pleasant to build against are the request shape, what gets cached and for how long, and how the API handles the moment you change a template that's already been used to generate thousands of live images. This is a look at those mechanics specifically, at the level of detail you'd want before wiring one into a production pipeline.

Request shape: template plus variables

A render request has two parts conceptually: a reference to a template (which defines layout, fonts, and static design elements) and a set of variable values (the specific text, image URL, or color for this particular render). Depending on the API, this is expressed either as a GET request with query parameters — convenient because the resulting URL is itself the shareable, embeddable asset — or as a POST request with a JSON body, which is more natural when you're passing longer text fields or binary image data rather than a URL reference.

GET-based rendering has a specific advantage worth calling out: because the entire request is encoded in the URL, that URL can be dropped directly into an og:image meta tag with no server-side step to first call the API and store a result. The URL itself is the asset. A crawler or browser requesting that URL is transparently triggering (or hitting the cache for) the same render the API would produce if you called it directly.

Response format

The response to a render request should be the image itself — typically PNG, served with the correct Content-Type header — not a JSON payload wrapping a base64-encoded image or a secondary URL you then have to fetch. That matters because og:image tags and most platform crawlers expect to request a URL and receive image bytes directly; adding an indirection step (call the API, get JSON, extract a URL, fetch that URL) adds latency and a point of failure that a directly renderable URL avoids entirely.

Caching: why immutability is the design that matters

The single most consequential design decision in an open graph image API is whether a given render URL is mutable or immutable — whether the same URL can produce a different image at different points in time. A mutable design (the URL always reflects the "current" state of a template) sounds convenient, but it means every cache layer between the API and the requester — CDN edge caches, platform-side caches like Facebook's, browser caches — has to be treated as potentially stale the moment you change anything, and there's no way to guarantee an old shared link still shows what it showed when it was shared.

An immutable design solves this at the URL level instead of the cache-invalidation level: a given version of a template always renders the same output for the same variable values, forever, so the URL is safe to cache aggressively and indefinitely, anywhere in the chain. useopengraph's render URLs follow this pattern explicitly — /api/render/{templateId}/v{version}/... — where publishing a template creates a new version, and every prior version stays exactly as it was, guaranteed at the caching and CDN level. That means a crawler hitting a link shared eight months ago gets the same image it would have gotten the day it was shared, even if the template has been redesigned three times since.

What versioning solves that invalidation can't

Cache invalidation — actively purging a stale cached copy when something changes — is the alternative approach, and it's genuinely harder to get right than it sounds. You need to purge every cache layer, including ones you don't fully control (a social platform's own cache of your image, for instance), and if you miss one, different requesters end up seeing different images for the same URL depending on which cache served them. Versioning sidesteps this problem entirely: instead of trying to invalidate a stale copy, you never reuse the URL for different content in the first place. Old links point at old versions; a redesign simply means new links (generated after publish) point at the new version. Nothing ever has to be purged.

Practical implications for how you integrate it

  • Store the rendered URL, not just the template ID, when you save a page's OG image reference — the version is part of what makes that link stable.
  • Treat republishing a template as creating new assets going forward, not editing existing ones — old pages keep their old version until you explicitly regenerate them against the new one.
  • Don't build your own cache-busting logic (appending random query strings to force a fresh render) — it defeats the CDN caching the API is designed to give you, and it's unnecessary once URLs are already immutable by version.
  • If you need a page's image to reflect a template redesign, that's a deliberate re-render against the new version, not something that should happen automatically to a URL that's already been shared.

Authentication and workspace scoping

Render requests that reference private or draft templates need authentication scoped to the workspace that owns them, typically via an API key. This is the same credential model used across the rest of a workspace-scoped API — projects, templates, audits, and sharing links all live under the same key rather than requiring separate credentials per feature, and the same key works whether the caller is a CMS webhook, a backfill script, or an MCP-connected AI agent.

Content negotiation and format support

Most open graph consumers expect PNG, but some rendering pipelines also need to support JPEG for smaller file sizes on photo-heavy templates, or need to respect a requested width and height rather than only producing one fixed output size. An API that supports content negotiation — letting the caller specify format and dimensions as part of the request rather than baking exactly one output shape into every template — saves having to build and maintain a separate template purely to get a second aspect ratio for a platform with different requirements.

Error responses that fail predictably

A render request referencing a template or version that doesn't exist, or missing a required variable, should return a clear, predictable error — a specific status code and a machine-readable reason — rather than a generic failure or, worse, a silently broken image response. This matters more than it sounds like it should, because these are exactly the failures that get triggered by expired links, deleted templates, or malformed integration code, and a script or an AI agent calling the API needs to be able to distinguish "this specific thing is wrong" from "something is broken" in order to respond appropriately rather than just retrying blindly.

How useopengraph implements this

useopengraph's render API is built around exactly this request/response/versioning model: templates are created and edited visually with text, image, and color variables, then rendered via a GET-friendly URL at /api/render/{templateId}/v{version}/... that returns image bytes directly, cached aggressively because each version is immutable by design. Publishing a new version of a template never touches the URLs already generated against older versions — so a marketing team can redesign a card layout on a Tuesday without worrying that every link shared last month is about to change out from under them.

Stop paying per seat
for a usage-shaped problem.

Unlimited teammates, one usage pool. Start free with the scanner — no card required.