arrow_back Back to blog
Comparisons March 6, 2026 7 min read

ScreenshotOne Alternative: Why Developers Are Switching to len.sh

ScreenshotOne is a solid screenshot API. It’s been around for a while, has decent documentation, and covers the core feature set most developers need. So why are developers switching to len.sh?

This post is a direct, honest comparison. We’ll cover the specific differences in API design, pricing, features, and developer experience — with code examples showing what migration actually looks like.


The Quick Version

If you’re short on time, here’s the summary:

Aspectlen.shScreenshotOne
Pricing (25K screenshots/mo)€19/mo$79/mo
Feature gatingNone — all features on all plansSome features locked to higher tiers
OG image generationDedicated /v1/og endpointNot available
PDF generationDedicated /v1/pdf endpointVia format parameter
Signed URLsYesNo
Custom JS/CSS injectionYesYes
SDKsJS, Python, Ruby, PHP, GoJS, Python, PHP
Free tier100 screenshots/mo100 screenshots/mo

API Design

The biggest difference you’ll notice immediately is API simplicity.

ScreenshotOne Request

curl "https://api.screenshotone.com/take?access_key=YOUR_KEY&url=https://example.com&viewport_width=1280&viewport_height=720&format=png&full_page=true&delay=1000"

len.sh Request

curl "https://api.len.sh/v1/screenshot?access_key=YOUR_API_KEY&url=https://example.com&width=1280&height=720&format=png&full_page=true&delay=1000"

The requests look similar, and they are. The core screenshot capture experience is comparable. Both take a URL, viewport dimensions, format, and delay. Both return raw image bytes.

The differences show up in three areas: dedicated endpoints, authentication flexibility, and parameter naming.

Dedicated Endpoints vs. Overloaded Parameters

len.sh separates concerns into distinct endpoints:

  • /v1/screenshot — capture screenshots
  • /v1/og — generate branded OG images
  • /v1/pdf — generate PDFs with paper size, margins, page ranges

ScreenshotOne handles everything through a single endpoint with format parameters. This works, but it means PDF options (paper size, margins, landscape) are mixed into the same parameter space as screenshot options, which can be confusing.

Authentication Flexibility

ScreenshotOne supports query parameter authentication only:

?access_key=YOUR_KEY

len.sh supports both query parameters and Bearer token headers:

# Query parameter
?access_key=YOUR_API_KEY

# Or Bearer header (recommended for production)
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.len.sh/v1/screenshot?url=https://example.com"

Header-based auth is better practice for production — it keeps API keys out of server logs and URL history. len.sh also supports signed URLs for public embedding, which ScreenshotOne doesn’t offer.


Pricing: The Concrete Difference

This is the most common reason developers switch. Let’s compare directly:

Monthly Volumelen.shScreenshotOneSavings
100 (free tier)€0$0
5,000€19$2934%
25,000€19$7976%
100,000€99$19950%
500,000€99CustomSignificant

At 25,000 screenshots per month, len.sh is $60/month cheaper — that’s $720/year saved on the same functionality. At 100,000 screenshots, the annual savings jump to $1,200.

Both services offer a free tier of 100 screenshots per month, so you can test before committing.

No Feature Gating

This matters more than it might seem. With len.sh, every feature is available on every plan — including the free tier. You get:

  • Full-page capture
  • Custom viewports and device scale
  • JS/CSS injection
  • Ad and cookie banner blocking
  • All output formats (PNG, JPEG, WebP)
  • OG image generation
  • PDF generation
  • Signed URLs
  • Element selector capture

With ScreenshotOne, some capabilities like higher concurrency and priority processing are reserved for higher-tier plans. This means you might build against the free tier, then discover you need to upgrade for a feature your production workflow requires.


Features len.sh Has That ScreenshotOne Doesn’t

1. Dedicated OG Image Generation

len.sh has a purpose-built endpoint for generating Open Graph images:

curl "https://api.len.sh/v1/og?title=My+Blog+Post&subtitle=A+deep+dive&badge=Tutorial&brand_name=Acme&brand_color=%234F46E5&theme=dark&access_key=YOUR_API_KEY" \
  --output og.png

This generates a branded 1200x630 OG image without building or hosting an HTML template. You pass structured parameters (title, subtitle, badge, brand name, brand color, theme) and get a production-ready social card.

With ScreenshotOne, you’d need to:

  1. Build an HTML template for your OG images
  2. Host it somewhere accessible
  3. Screenshot that template URL at 1200x630
  4. Handle template styling, fonts, and responsiveness yourself

The len.sh /v1/og endpoint eliminates all of that.

2. Signed URLs

Signed URLs let you embed screenshots in client-side contexts — <img> tags, emails, public pages — without exposing your API key:

import { signUrl } from "@lensh/sdk";

const url = signUrl(
  "https://api.len.sh/v1/screenshot?url=https://example.com&width=1280",
  { keyId: "your-key-id", signingSecret: "your-signing-secret" }
);

// Safe to use in public HTML:
// <img src="${url}" />

The URL is authenticated via HMAC-SHA256 signature, so the end user never sees your API key. This is essential for email templates, public dashboards, and any context where the screenshot URL is visible to users.

ScreenshotOne doesn’t offer this feature. You’d need to proxy requests through your own server to keep the API key hidden.

3. Dedicated PDF Endpoint

len.sh’s /v1/pdf endpoint has purpose-built parameters for PDF generation:

curl "https://api.len.sh/v1/pdf?url=https://example.com/invoice&paper_size=A4&margin_top=1in&margin_bottom=1in&print_background=true&access_key=YOUR_API_KEY" \
  --output invoice.pdf

PDF-specific parameters include:

ParameterDescription
paper_sizeA4, Letter, Legal, Tabloid, Ledger
landscapeLandscape orientation
margin_top/right/bottom/leftCSS margin values
print_backgroundInclude background colors/images
page_rangesSpecific pages, e.g. “1-3, 5”
header_templateCustom HTML header
footer_templateCustom HTML footer
scaleScale factor (0.1-2.0)

Migration Guide

Switching from ScreenshotOne to len.sh is straightforward. The API patterns are similar enough that migration is mostly a find-and-replace operation.

JavaScript Migration

Before (ScreenshotOne):

async function takeScreenshot(url) {
  const params = new URLSearchParams({
    access_key: process.env.SCREENSHOTONE_KEY,
    url: url,
    viewport_width: "1280",
    viewport_height: "720",
    format: "png",
    full_page: "true",
  });

  const response = await fetch(
    `https://api.screenshotone.com/take?${params}`
  );
  return response.arrayBuffer();
}

After (len.sh):

async function takeScreenshot(url) {
  const params = new URLSearchParams({
    access_key: process.env.LENSH_API_KEY,
    url: url,
    width: "1280",
    height: "720",
    format: "png",
    full_page: "true",
  });

  const response = await fetch(
    `https://api.len.sh/v1/screenshot?${params}`
  );
  return response.arrayBuffer();
}

The changes:

  1. Base URL: api.screenshotone.com/take to api.len.sh/v1/screenshot
  2. Parameter names: viewport_width to width, viewport_height to height
  3. API key env var name (your choice)

Python Migration

Before (ScreenshotOne):

import requests

def take_screenshot(url: str) -> bytes:
    params = {
        "access_key": os.environ["SCREENSHOTONE_KEY"],
        "url": url,
        "viewport_width": 1280,
        "viewport_height": 720,
        "format": "png",
        "full_page": "true",
    }
    response = requests.get(
        "https://api.screenshotone.com/take",
        params=params,
    )
    response.raise_for_status()
    return response.content

After (len.sh):

import requests

def take_screenshot(url: str) -> bytes:
    params = {
        "access_key": os.environ["LENSH_API_KEY"],
        "url": url,
        "width": 1280,
        "height": 720,
        "format": "png",
        "full_page": "true",
    }
    response = requests.get(
        "https://api.len.sh/v1/screenshot",
        params=params,
    )
    response.raise_for_status()
    return response.content

Same pattern. Change the base URL, rename the viewport parameters, swap the key. Done.

Using the len.sh SDK

If you want to use the official SDK instead of raw HTTP:

import { signUrl } from "@lensh/sdk";

// Generate a signed URL for public embedding
const publicUrl = signUrl(
  "https://api.len.sh/v1/screenshot?url=https://example.com&width=1280&format=webp",
  { keyId: "your-key-id", signingSecret: "your-signing-secret" }
);
from lensh import sign_url

# Generate a signed URL for public embedding
public_url = sign_url(
    "https://api.len.sh/v1/screenshot?url=https://example.com&width=1280&format=webp",
    key_id="your-key-id",
    signing_secret="your-signing-secret",
)

What ScreenshotOne Does Better

We want to be honest about the tradeoffs:

  • Geo-targeted rendering — ScreenshotOne lets you specify the geographic location of the rendering browser. len.sh doesn’t offer this yet.
  • Longer track record — ScreenshotOne has been around longer, which matters for risk-averse enterprise buyers.
  • Async webhooks — ScreenshotOne supports webhook callbacks for async screenshot processing. len.sh is synchronous only.

If any of these are critical for your use case, ScreenshotOne might still be the better choice.


The Bottom Line

For most screenshot API use cases, len.sh offers:

  • Lower cost — 50-76% cheaper at comparable volumes
  • No feature gating — everything available on every plan
  • More built-in functionality — dedicated OG image and PDF endpoints, signed URLs
  • Broader SDK support — official SDKs in five languages
  • Simpler migration — similar API patterns, mostly a URL and parameter rename

The migration takes about 15 minutes for a typical integration.

Ready to switch? Sign up for a free len.sh account and test your existing URLs against the API before making any code changes. The free tier gives you 100 screenshots per month to validate the integration.

Try len.sh for free

Start capturing screenshots with a simple API call. No credit card required.