Resize screenshots

output.resize scales the captured image to the size you ask for, in the same request. The page is still laid out at the viewport size, so a desktop page stays a desktop page, only smaller. This guide shows the request, compares the five fit modes with real numbers and explains when a smaller viewport or a different scale is the better tool. More guides are on the guides hub.

The request

Capture a 1280x800 page and return a 640x400 WebP thumbnail:

curl

curl -sS --fail -X POST https://api.renderscreenshot.com/v1/screenshot \
  -H "Authorization: Bearer $RS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "url": "https://example.com",
      "viewport": {
        "width": 1280,
        "height": 800
      },
      "output": {
        "format": "webp",
        "quality": 85,
        "resize": {
          "width": 640,
          "height": 400,
          "fit": "cover"
        }
      }
    }' \
  -o thumb.webp

Node.js

import { writeFile } from 'node:fs/promises';

const body = {
  "url": "https://example.com",
  "viewport": {
    "width": 1280,
    "height": 800
  },
  "output": {
    "format": "webp",
    "quality": 85,
    "resize": {
      "width": 640,
      "height": 400,
      "fit": "cover"
    }
  }
};

const res = await fetch('https://api.renderscreenshot.com/v1/screenshot', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.RS_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});

if (!res.ok) {
  console.error(res.status, await res.text());
  process.exit(1);
}

await writeFile('thumb.webp', Buffer.from(await res.arrayBuffer()));

Python

import json
import os
import sys
import urllib.error
import urllib.request

body = {
    'url': 'https://example.com',
    'viewport': {
        'width': 1280,
        'height': 800,
    },
    'output': {
        'format': 'webp',
        'quality': 85,
        'resize': {
            'width': 640,
            'height': 400,
            'fit': 'cover',
        },
    },
}

req = urllib.request.Request(
    'https://api.renderscreenshot.com/v1/screenshot',
    data=json.dumps(body).encode(),
    headers={
        'Authorization': f"Bearer {os.environ['RS_API_KEY']}",
        'Content-Type': 'application/json',
    },
    method='POST',
)

try:
    with urllib.request.urlopen(req, timeout=90) as res:
        with open('thumb.webp', 'wb') as f:
            f.write(res.read())
except urllib.error.HTTPError as err:
    sys.exit(f'{err.code} {err.read().decode()}')

Ruby

require 'json'
require 'net/http'

body = {
  url: 'https://example.com',
  viewport: {
    width: 1280,
    height: 800
  },
  output: {
    format: 'webp',
    quality: 85,
    resize: {
      width: 640,
      height: 400,
      fit: 'cover'
    }
  }
}

uri = URI('https://api.renderscreenshot.com/v1/screenshot')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{ENV.fetch('RS_API_KEY')}"
request['Content-Type'] = 'application/json'
request.body = body.to_json

response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: 90) do |http|
  http.request(request)
end
abort("#{response.code} #{response.body}") unless response.is_a?(Net::HTTPSuccess)

File.binwrite('thumb.webp', response.body)

PHP

<?php

$body = [
    'url' => 'https://example.com',
    'viewport' => [
        'width' => 1280,
        'height' => 800,
    ],
    'output' => [
        'format' => 'webp',
        'quality' => 85,
        'resize' => [
            'width' => 640,
            'height' => 400,
            'fit' => 'cover',
        ],
    ],
];

$ch = curl_init('https://api.renderscreenshot.com/v1/screenshot');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('RS_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 90,
]);

$image = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($image === false || $status !== 200) {
    fwrite(STDERR, $status . ' ' . ($image === false ? curl_error($ch) : $image) . "\n");
    exit(1);
}

file_put_contents('thumb.webp', $image);

Go

package main

import (
    "bytes"
    "io"
    "log"
    "net/http"
    "os"
)

const body = `{
  "url": "https://example.com",
  "viewport": {
    "width": 1280,
    "height": 800
  },
  "output": {
    "format": "webp",
    "quality": 85,
    "resize": {
      "width": 640,
      "height": 400,
      "fit": "cover"
    }
  }
}`

func main() {
    req, err := http.NewRequest(http.MethodPost, "https://api.renderscreenshot.com/v1/screenshot", bytes.NewBufferString(body))
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("RS_API_KEY"))
    req.Header.Set("Content-Type", "application/json")

    res, err := http.DefaultClient.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer res.Body.Close()

    data, err := io.ReadAll(res.Body)
    if err != nil {
        log.Fatal(err)
    }
    if res.StatusCode != http.StatusOK {
        log.Fatalf("%d %s", res.StatusCode, data)
    }

    if err := os.WriteFile("thumb.webp", data, 0o644); err != nil {
        log.Fatal(err)
    }
}

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;

public class Capture {
    public static void main(String[] args) throws Exception {
        String body = """
            {
              "url": "https://example.com",
              "viewport": {
                "width": 1280,
                "height": 800
              },
              "output": {
                "format": "webp",
                "quality": 85,
                "resize": {
                  "width": 640,
                  "height": 400,
                  "fit": "cover"
                }
              }
            }
            """;

        HttpRequest request = HttpRequest.newBuilder(URI.create("https://api.renderscreenshot.com/v1/screenshot"))
            .header("Authorization", "Bearer " + System.getenv("RS_API_KEY"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(body))
            .build();

        HttpResponse<byte[]> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofByteArray());
        if (response.statusCode() != 200) {
            System.err.println(response.statusCode() + " " + new String(response.body()));
            System.exit(1);
        }

        Files.write(Path.of("thumb.webp"), response.body());
    }
}

The capture and the target have the same 16:10 shape, so every fit mode returns the same image here. The modes differ when the shapes differ.

The five fit modes

fit Keeps the aspect ratio Output size What happens to the extra space
contain (default) Yes Exactly the box The whole image fits inside; the rest of the box is padding
cover Yes Exactly the box The image fills the box; the overflow is cropped
fill No Exactly the box The image is stretched to the box
inside Yes At most the box The whole image fits; no padding, so one side is shorter
outside Yes At least the box The image covers the box; nothing is cropped, so one side is longer

These are the same five names that image libraries such as sharp use. The numbers below come from sharp 0.35.5, run on 2026-10-15, resizing a 1280x800 image into a 600x600 box:

fit Result Notes
cover 600x600 Scaled to 960x600, then 180 px cut from each side
contain 600x600 Scaled to 600x375, then padded top and bottom
fill 600x600 Text and logos look squeezed
inside 600x375 No padding, no crop
outside 960x600 No crop; wider than the box

For screenshots, cover and inside are the usual choices. cover gives a fixed-size tile for a grid and cuts the edges of the page. inside keeps the whole page and lets the size vary. fill distorts text, so it is rarely what you want for a web page.

Resize, viewport or scale?

The three settings change different things:

Setting Changes the page layout Changes pixel density Use it for
viewport.width / height Yes. A 390 px viewport gets the mobile layout No Mobile captures, layout at a given breakpoint
viewport.scale No Yes. 2 gives twice the pixels per CSS pixel Sharp images for high-density screens
output.resize No No. It scales the finished image Thumbnails and fixed-size tiles of the desktop layout

Two patterns use them together:

  • A sharp thumbnail for high-density screens. Capture at scale: 2, then resize to twice the display size. A tile shown at 320x200 CSS pixels gets resize 640x400.
  • A fixed social card from any page. Use the og_card preset for the 1200x630 viewport. You do not need resize, because the capture already has the right size.

Several sizes from one capture

Each request is one render and uses one credit, so three sizes through output.resize cost three credits. If you need several sizes of the same capture, request the largest one and resize on your side. Tested on 2026-10-15 with Node.js 22.18 and sharp 0.35.5:

npm install [email protected]
// Make several sizes from one capture, so you pay for one render.
// Usage: node sizes.mjs capture.png
import sharp from 'sharp';

const input = process.argv[2] ?? 'capture.png';
const sizes = [
  { name: 'card', width: 1200, height: 630, fit: 'cover' },
  { name: 'thumb', width: 400, height: 250, fit: 'cover' },
  { name: 'square', width: 600, height: 600, fit: 'inside' },
];

for (const { name, width, height, fit } of sizes) {
  const info = await sharp(input)
    .resize({ width, height, fit, withoutEnlargement: true })
    .webp({ quality: 85 })
    .toFile(`${name}.webp`);
  console.log(name, `${info.width}x${info.height}`);
}

On a 1280x800 capture this printed card 1200x630, thumb 400x250 and square 600x375.

Parameters that matter

Parameter Type Default Notes
output.resize.width integer none Target width in pixels
output.resize.height integer none Target height in pixels
output.resize.fit string contain contain, cover, fill, inside, outside
output.format string png WebP or JPEG make smaller thumbnails
output.quality integer 80 1 to 100, JPEG and WebP only
viewport.scale number 1 1 to 3

Width and height each accept 1 to 7680. Send at least one of them: with only one, the other follows the aspect ratio and fit does not apply. The padding that contain adds is white, or transparent when output.transparent is true. An unknown fit value or an out-of-range size returns invalid_parameter.

output.resize is a POST option. The output options reference and the viewport reference list the rest.

Troubleshooting

Symptom Cause Fix
Thumbnail shows the desktop layout, you wanted mobile Resize does not change the layout Set a mobile viewport.width instead.
Edges of the page are missing fit: "cover" crops Use inside or contain.
Text looks squeezed fit: "fill" ignores the aspect ratio Use cover or inside.
Blurry thumbnail on a phone Resized below the display's pixel density Capture at scale: 2 and resize to twice the display size.
Bars around the image fit: "contain", the default, pads Use cover for a full tile or inside for no padding.

Related in this topic

Was this page helpful?