Open Graph image API
An Open Graph image is the card that shows when a link is shared on Slack, LinkedIn or X. Shotkiln generates them at 1200 × 630 from a title and a template, no design tool needed.
- Endpoint
GETorPOST /v1/og- Output
- 1200 × 630 PNG
- Templates
default,split,gradient; light or dark theme; any hex accent- Text
- title up to 200 characters (font size adapts), subtitle up to 300, site name up to 60
- Cache
- 7 days by default; repeat requests use no quota
- Price
- same quota as screenshots: 100 free a month, $9 for 2,000
The three templates



Generate cards at build time
Fetch one card per page during your static build and save it next to the page. The key stays in your CI secrets and the card is served from your own domain.
for (const post of posts) {
const q = new URLSearchParams({ title: post.title, subtitle: post.date, site: "acme.dev", template: "split" });
const res = await fetch("https://shotkiln.com/v1/og?" + q, { headers: { "x-api-key": process.env.SHOTKILN_KEY } });
await fs.writeFile(`public/og/${post.slug}.png`, Buffer.from(await res.arrayBuffer()));
}<meta property="og:image" content="https://acme.dev/og/release-2-4.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta name="twitter:card" content="summary_large_image">Signed URLs for og:image tags
A signed URL points the og:image tag at Shotkiln directly, so there is no build step and no file to host. The tag holds your key id and a signature, never the API key, and nobody can change the text to draw other cards on your quota. Get the secret once with POST /v1/keys/signing-secret, then sign each URL on your server. The docs have the full algorithm and a Python version.
import { createHmac } from "node:crypto";
const enc = encodeURIComponent;
const params = { title: post.title, template: "split", site: "acme.dev", kid: process.env.SHOTKILN_KID };
const qs = Object.entries(params).sort(([a], [b]) => (a < b ? -1 : 1)).map(([k, v]) => enc(k) + "=" + enc(v)).join("&");
const sig = createHmac("sha256", process.env.SHOTKILN_SIGNING_SECRET).update("/v1/og?" + qs).digest("hex");
const ogImage = "https://shotkiln.com/v1/og?" + qs + "&sig=" + sig;Next.js metadata
In the App Router, generate the card when the page is built or revalidated, store it, and point openGraph.images at the stored file. Or put a signed URL in openGraph.images and skip storing the file.
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
openGraph: { images: [{ url: `/og/${post.slug}.png`, width: 1200, height: 630 }] },
};
}Compared with other OG image tools
@vercel/og (Satori) runs in your own app and is free, but supports a subset of CSS and you maintain the fonts and layout. HTML/CSS to Image ($14 for 1,000) and Bannerbear ($49 for 1,000 credits) render custom designs from template editors. CaptureAPI also has an OG endpoint, with Starter at the same $4.50 per 1,000 as Shotkiln. Shotkiln has fixed templates instead of an editor, and signed URLs so the card URL can sit in an og:image tag.
Frequently asked questions
- What size should an Open Graph image be?
- 1200 × 630 pixels, about 1.91 to 1. Facebook, LinkedIn, Slack, Discord and X show that size without cropping; keep text 60 pixels from the edges.
- Does the OG endpoint need an API key?
- No. Without a key, cards carry a small "made with Shotkiln" label, and a paid key removes it. To use your key in a public og:image tag, sign the URL so the key stays private.
- How do signed OG image URLs work?
- The URL carries your key id and an HMAC-SHA256 signature of its parameters, so anyone can load the card but nobody can change it. Add exp for an expiry, and rotate the secret to revoke old URLs.
- Should an og:image be a screenshot of the page?
- Usually not: a 1200 × 630 screenshot is too small to read in a feed. A title card is clearer, so keep screenshots for previews of external sites.
- Can I use my own HTML design for the card?
- Not on /v1/og, which uses three templates with your text, theme and accent colour. For fully custom designs, HTML/CSS to Image and Bannerbear have template editors.
- How do I update a card after changing the title?
- Change any parameter and you get a new image. Cards are cached for 7 days by default, and social networks keep their own copy, so refresh it with their debugger tools.