On this page

    Build features

    Show Link Previews in Your App

    Turn a URL that a user pastes into a preview card with a thumbnail, title, description and icon. One request, JSON back, cached for repeat links.

    Time
    20 minutes
    Plan
    Free
    You need
    • An API key in RS_API_KEY, on your server
    • A place to store a preview per URL (a database table or cache)

    What you will have at the end

    The card is built from one API response: the thumbnail, the title, the description and the favicon.

    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 }
    }
    
    image.url: the 800×600 thumbnail. A narrow window, so many sites show their tablet layout.

    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.url is a public CDN link that needs no key, so you can put it in an <img> tag. It is kept until cache.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.

    Many users paste the same links. Two layers keep this cheap:

    1. Your database first. Look up the URL before you call the API. Fetch again only when the stored image_expires_at has passed.
    2. 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 }.
    In Logs, a cached request shows 0 credits.

    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:// and https:// URLs. Reject javascript:, 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

    Try RenderScreenshot free

    150 free credits every month, no credit card required.