When a user pastes a link into your app, you can show a card with a thumbnail of the page, its title and its description, as chat apps do. One API request returns all of it. You make the request on your server, store the result, and show it from your own database after that.
Request the preview as JSON
Send the URL with the link_preview preset (800×600) and the header Accept: application/json. With that header the API returns JSON with a link to the image, not the image bytes.
curl -X POST https://api.renderscreenshot.com/v1/screenshot \ -H "Authorization: Bearer $RS_API_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "url": "https://renderscreenshot.com/blog/generating-social-cards-with-screenshots", "preset": "link_preview", "cache": { "available": true, "ttl": 2592000 }, "block": { "cookie_banners": true } }'
The response (shortened):
{ "id": "req_e970a98af076743e", "status": "completed", "image": { "url": "https://cdn.renderscreenshot.com/screenshots/.../fc1486ee17dc.png", "width": 800, "height": 600, "format": "png" }, "metadata": { "title": "Generate OG Images From HTML: A Production Guide - RenderScreenshot", "description": "Generate Open Graph images from an HTML template: platform size limits, escaping, ...", "favicon": "https://renderscreenshot.com/icon.png" }, "cache": { "hit": false, "expires_at": "2026-11-24T01:59:29Z" }, "usage": { "credits": 1, "remaining": 2499 } }
Save the image URL and the page details
Store the four values with the link. This Node.js function (Express, Next.js route or any server) does the request and returns what to save:
Node.js
export async function fetchPreview(url) { const response = await fetch('https://api.renderscreenshot.com/v1/screenshot', { method: 'POST', headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json', }, body: JSON.stringify({ url, preset: 'link_preview', cache: { available: true, ttl: 2592000 }, block: { cookie_banners: true }, }), }); const body = await response.json(); if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`); return { url, imageUrl: body.image.url, imageExpiresAt: body.cache.expires_at, title: body.metadata.title ?? url, description: body.metadata.description ?? '', favicon: body.metadata.favicon ?? null, }; }
Python
import os import requests def fetch_preview(url): response = requests.post( 'https://api.renderscreenshot.com/v1/screenshot', headers={ 'Authorization': f"Bearer {os.environ['RS_API_KEY']}", 'Accept': 'application/json', }, json={ 'url': url, 'preset': 'link_preview', 'cache': {'available': True, 'ttl': 2592000}, 'block': {'cookie_banners': True}, }, timeout=60, ) body = response.json() if not response.ok: raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}") metadata = body.get('metadata', {}) return { 'url': url, 'image_url': body['image']['url'], 'image_expires_at': body['cache']['expires_at'], 'title': metadata.get('title') or url, 'description': metadata.get('description') or '', 'favicon': metadata.get('favicon'), }
Ruby
require 'net/http' require 'json' def fetch_preview(url) uri = URI('https://api.renderscreenshot.com/v1/screenshot') request = Net::HTTP::Post.new(uri, 'Authorization' => "Bearer #{ENV.fetch('RS_API_KEY')}", 'Content-Type' => 'application/json', 'Accept' => 'application/json') request.body = { url: url, preset: 'link_preview', cache: { available: true, ttl: 2_592_000 }, block: { cookie_banners: true } }.to_json response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60) { |http| http.request(request) } body = JSON.parse(response.body) raise "#{body.dig('error', 'code')}: #{body.dig('error', 'message')}" unless response.is_a?(Net::HTTPSuccess) metadata = body.fetch('metadata', {}) { url: url, image_url: body.dig('image', 'url'), image_expires_at: body.dig('cache', 'expires_at'), title: metadata['title'] || url, description: metadata['description'].to_s, favicon: metadata['favicon'] } end
image.urlis a public CDN link that needs no key, so you can put it in an<img>tag. It is kept untilcache.expires_at(at most 30 days). To keep previews longer, copy the image to your own storage, or save screenshots to your bucket directly.
Show the card
Render the stored values. Escape the title and description like any other user content.
<a class="link-card" href="https://renderscreenshot.com/blog/generating-social-cards-with-screenshots"> <img class="link-card-image" src="https://cdn.renderscreenshot.com/screenshots/.../fc1486ee17dc.png" width="800" height="600" alt="" loading="lazy"> <span class="link-card-body"> <span class="link-card-site"> <img src="https://renderscreenshot.com/icon.png" width="16" height="16" alt=""> renderscreenshot.com </span> <strong>Generate OG Images From HTML: A Production Guide</strong> <span>Generate Open Graph images from an HTML template: platform size limits, escaping, ...</span> </span> </a>
The card at the top of this guide is this markup with a little CSS.
Reuse previews for repeat links
Many users paste the same links. Two layers keep this cheap:
- Your database first. Look up the URL before you call the API. Fetch again only when the stored
image_expires_athas passed. - The API cache second. With
cache.available: true, an identical request inside the TTL returns the earlier render for 0 credits. The response then has"cache": { "hit": true }and"usage": { "credits": 0 }.
Reuse screenshots for 0 credits explains the cache key and the TTL.
Accept only safe URLs
You send URLs that strangers typed, so check them before you call the API:
- Accept only
http://andhttps://URLs. Rejectjavascript:,file:and others. - Limit how many previews one user can request per minute, so one user cannot use your whole credit balance.
- Make the request from a background job if the page can wait. A render takes 2 to 5 seconds.
The API also protects you: it refuses to load private, loopback and cloud metadata addresses, for the page and for everything the page loads. Such a request fails with the blocked error and costs nothing.
Next steps
- Capture many pages at once to fill previews for an existing list of links.
- Add social share images to your own pages.
- Remove cookie banners and ads from thumbnails.