RenderScreenshot is an OG image API that renders your own HTML in Chromium. Design the card as a page on your site, 1200×630, with the CSS and fonts you already use. Then point og:image at one signed URL with preset=og_card. When a crawler fetches it, the API loads your page, captures it and returns the PNG in the same response.
curl -G https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ --data-urlencode "url=https://example.com/og/hello-world?v=3" \ -d preset=og_card \ -o card.png
That is the test from a terminal. In production the same request becomes a signed URL in your <head>, so no key ever ships to the browser. The step-by-step OG image tutorial builds the whole setup with a Next.js App Router example. On WordPress, the Yoast and Rank Math plugin file does the same in PHP.
Why a screenshot API for OG images
The other ways to generate social cards each ask you to learn a second layout system:
- JSX-to-SVG engines such as Satori, the engine inside
@vercel/og, support flexbox and a subset of CSS. In our tests, CSS grid andbackdrop-filterrendered without an error but with a different result than Chromium. See the Vercel OG alternative for the test. - Template editors such as Bannerbear and Placid keep the design in their editor and fill named layers through an API. Good for marketing teams, but your cards no longer live in your repo. See the Bannerbear alternative.
- Self-hosted Puppeteer gives full CSS, and also a Chromium process to keep alive, fonts to install and a queue to run.
With an HTML template rendered by Chromium, the card is just another page. It shares your design tokens, your web fonts and your components. Designers can change it with the same pull request as the site.
What the API gives you for social cards
Presets for each card size. You do not need to remember dimensions:
| Preset | Viewport | Format | Default cache | Use it for |
|---|---|---|---|---|
og_card |
1200×630 | PNG | 24 hours | og:image for Facebook, LinkedIn, Slack, Discord |
twitter_card |
1200×600 | PNG | 24 hours | X summary_large_image, exact 2:1 |
twitter_card_large |
1200×628 | PNG | 24 hours | X large cards close to 1.91:1 |
link_preview |
800×600 | PNG | default | Chat-style previews with page metadata |
Any preset value can be overridden in the same request, for example format=jpeg for a photographic background. The presets reference lists every setting.
Signed URLs that go straight into the tag. A signed URL is a plain GET that returns image/png, so the tag points at the image itself, with no redirect route in your app. Signed URLs work for GET and HEAD only, the expiry is at most 30 days ahead, and changing any parameter after signing returns invalid_signature. Nobody can reuse your URL to render a different page on your account.
A public CDN link on every response. Each image response carries X-Cache-URL, a link to the stored file on our CDN that needs no authentication. If you prefer to render cards on your server when a post is published, store that link and use it as og:image until X-Cache-Expires. On Ghost, an Admin API script renders each card once and uploads it to the post, so it does not expire.
Zero-credit repeats. Crawlers fetch the same image many times. Sign cache_available=true (and cache_ttl up to 2592000 seconds, 30 days) into the URL, and each repeat fetch inside the TTL reuses the first render for 0 credits. The caching reference explains the cache key.
Page metadata for free. Responses include X-Screenshot-Title, X-Screenshot-Description and X-Screenshot-Favicon, read from the rendered page. That is handy when you build link previews of other people's URLs, not only cards for your own.
How a request flows
flowchart LR
A[Crawler reads og:image] --> B[GET /v1/screenshot signed URL]
B --> C{Signature valid and not expired?}
C -- no --> E[401 JSON error]
C -- yes --> D{cache_available and same card inside TTL?}
D -- yes --> F[Stored PNG, 0 credits]
D -- no --> G[Chromium on Cloudflare loads your template]
G --> H[PNG stored in R2 and served from the CDN]
H --> I[PNG returned, 1 credit, X-Cache-URL header]
We built the renderer on Cloudflare Browser Rendering, with images stored in R2. Rails checks the signature, the cache and the credits before a browser starts, so a bad URL fails fast and is never billed.
Minimal integration in six languages
Each SDK signs URLs with your public key ID (rs_pub_...) and secret key (rs_secret_...). The secret stays on your server. These calls follow the SDK reference pages; expiry must be within 30 days. Without an SDK, the SvelteKit example signs with Web Crypto.
// Node.js import { Client, TakeOptions } from 'renderscreenshot'; const client = new Client(process.env.RS_API_KEY, { signingKey: process.env.RS_SECRET_KEY, publicKeyId: process.env.RS_PUBLIC_KEY_ID, }); const ogImage = client.generateUrl( TakeOptions.url('https://example.com/og/hello-world?v=3').preset('og_card'), new Date(Date.now() + 21 * 24 * 3600 * 1000), );
# Python from datetime import datetime, timedelta, timezone from renderscreenshot import Client, TakeOptions client = Client(api_key, signing_key=secret_key, public_key_id=public_key_id) og_image = client.generate_url( TakeOptions.url('https://example.com/og/hello-world?v=3').preset('og_card'), datetime.now(timezone.utc) + timedelta(days=21), )
# Ruby client = RenderScreenshot::Client.new(api_key, signing_key: secret_key, public_key_id: public_key_id) og_image = client.generate_url( RenderScreenshot::TakeOptions.url('https://example.com/og/hello-world?v=3').preset('og_card'), expires_at: Time.now + (21 * 24 * 3600) )
<?php // PHP use RenderScreenshot\Client; use RenderScreenshot\TakeOptions; $client = new Client($apiKey, signingKey: $secretKey, publicKeyId: $publicKeyId); $ogImage = $client->generateUrl( TakeOptions::url('https://example.com/og/hello-world?v=3')->preset('og_card'), new DateTime('+21 days') );
// Go client, _ := rs.New(apiKey, rs.WithSigningKey(secretKey), rs.WithPublicKeyID(publicKeyID)) ogImage, err := client.GenerateURL( rs.URL("https://example.com/og/hello-world?v=3").Preset("og_card"), time.Now().Add(21*24*time.Hour), "", "", )
// Java Client client = new Client(apiKey, null, null, signingKey, publicKeyId); // null = default base URL and timeout String ogImage = client.generateUrl( TakeOptions.url("https://example.com/og/hello-world?v=3").preset("og_card"), Instant.now().plus(Duration.ofDays(21)) );
To sign cache_available=true as well, use the dependency-free signing functions on the signed URLs page, which accept any flat GET parameter.
Settings we recommend for cards
| Setting | Value | Reason |
|---|---|---|
preset |
og_card |
Correct size and format in one word |
cache_available |
true |
Crawler repeats cost 0 credits |
cache_ttl |
2592000 |
Cards change only when you change them; 30 days is the maximum |
| Version in the template URL | ?v=<updated_at> |
An edit gives a new cache key and a new image URL |
| Signed URL expiry | 21 to 28 days, bucketed by week | Stable URL for a week, never close to expiring when handed out |
format |
leave PNG for flat designs | Sharp text; switch to jpeg for photos |
What it costs
One fresh render is one credit, a reused render is free, and a failed render is not charged. With cache_available=true and a 30-day TTL, each card is rendered at most about once a month plus once per edit, however often it is shared, because repeat crawler fetches hit the cache. So a blog needs roughly one credit per actively shared post per month. If that count is under 150, the free plan covers it.
A product with user-generated pages is different. At 10,000 new or changed cards a month, the Growth plan at $40 a month includes 12,000 credits, which works out to $4.00 per 1,000 cards at that volume. For smaller sites, Starter is $10 a month for 2,500 credits. All plans are on the pricing page.
When to pick something else
- Your cards are simple flex layouts and you already host on Vercel:
ImageResponseis built in and needs no account. - Non-developers must edit card designs visually without a deploy: a template editor fits that team better.
- You need an image URL that never expires and you do not want to run storage: our CDN links last up to 30 days, so plan to copy cards into your own bucket.
Start
Create a free account, create a signing key pair under API Keys, and point one og:image at a signed og_card URL. The tutorial covers escaping, cache busting and platform limits, and the comparison of OG image generators shows where each tool fits.