Quickstart
In this guide, you'll sign your first OG image URL and render it. Everything is free under 500 renders/month.
Step 1: Get an API Key
Visit your RenderOG dashboard and click "Create API Key". A key like sk_live_a1b2c3d4e5f6... (40 hex chars) will appear exactly once.
Store it safely — you'll need it to view usage and rotate keys. Local dev? Create a .env file:
SIGNING_SECRET=your_signing_secret_here
Step 2: Understand the Signing Algorithm
All GET /og requests are HMAC-SHA256 signed to prevent tampering.
Steps:
- Collect all parameters except
sig:template=basic&title=Hello%20World&theme=dark&w=1200&h=630 - Sort alphabetically by key (already sorted in that example)
- Compute
sig = HMAC-SHA256(SIGNING_SECRET, canonical)as hex - Append to the URL:
...&sig=<hex>
Example (JavaScript):
const crypto = require('crypto');
const params = {
template: 'basic',
title: 'My First OG Image',
theme: 'dark',
w: '1200',
h: '630',
};
// Sort and encode
const sorted = Object.keys(params)
.sort()
.map(k => `${k}=${encodeURIComponent(params[k])}`)
.join('&');
const secret = process.env.SIGNING_SECRET;
const sig = crypto
.createHmac('sha256', secret)
.update(sorted)
.digest('hex');
const url = `https://api.renderog.app/og?${sorted}&sig=${sig}`;
console.log(url);
Step 3: Render with curl
Copy the signed URL from above and fetch it:
curl -s "https://api.renderog.app/og?template=basic&title=My%20First%20OG%20Image&theme=dark&w=1200&h=630&sig=abc123..." \
-o image.png
Open image.png in your browser. You've rendered an OG image at the edge.
Check performance headers:
curl -i "https://api.renderog.app/og?template=basic&title=My%20First%20OG%20Image&theme=dark&w=1200&h=630&sig=abc123..." | head -20
Look for:
X-Render-Ms: 45— milliseconds to render (first time) or near-zero (cache hit)X-Cache: MISS(first render) orHIT(cached)Cache-Control: public, s-maxage=31536000, immutable— cached for 1 year at the edge
Step 4: Render with Fetch (JavaScript)
Modern Node or browser:
async function renderOG() {
const params = {
template: 'basic',
title: 'My First OG Image',
theme: 'dark',
};
// Sign (use the crypto snippet from Step 2)
const sorted = Object.keys(params)
.sort()
.map(k => `${k}=${encodeURIComponent(params[k])}`)
.join('&');
const sig = crypto
.createHmac('sha256', process.env.SIGNING_SECRET)
.update(sorted)
.digest('hex');
const url = `https://api.renderog.app/og?${sorted}&sig=${sig}`;
const res = await fetch(url);
const buffer = await res.arrayBuffer();
const ms = res.headers.get('x-render-ms');
const cached = res.headers.get('x-cache');
console.log(`Rendered in ${ms}ms (${cached})`);
return buffer; // PNG bytes
}
renderOG();
Step 5: Use in Next.js Metadata
Generate a signed URL at build time (or on-the-fly in a server function) and use it in your metadata:
// app/page.tsx
import crypto from 'crypto';
function signOG(params) {
const sorted = Object.keys(params)
.sort()
.map(k => `${k}=${encodeURIComponent(params[k])}`)
.join('&');
const sig = crypto
.createHmac('sha256', process.env.SIGNING_SECRET)
.update(sorted)
.digest('hex');
return `https://api.renderog.app/og?${sorted}&sig=${sig}`;
}
export const metadata = {
title: 'My Blog Post',
openGraph: {
images: [
{
url: signOG({
template: 'blog',
title: 'My Blog Post',
subtitle: 'Published today',
theme: 'dark',
}),
width: 1200,
height: 630,
},
],
},
};
export default function Page() {
return <h1>My Blog Post</h1>;
}
What's Next?
- OG Reference — All four templates, every parameter, CSS constraints
- Pricing & Usage — Check your usage, understand billing
- Playground — Live template editor with preview
Debugging
403 invalid_signature — Double-check that SIGNING_SECRET matches your key, and that all parameter values are URL-encoded identically during signing and in the final URL.
400 invalid_params — Check that title is ≤200 chars, w/h are within limits (max 2400×1260), and template is one of: basic, gradient, blog, product.
422 render_failed — The rendering engine encountered an error (usually bad CSS or invalid template HTML). Check the browser console if using the playground; test with simple templates first.
429 rate_limited — You've exceeded the rate limit (60/min free, 600/min paid). Check the Retry-After header.