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:
| Aspect | len.sh | ScreenshotOne |
|---|---|---|
| Pricing (25K screenshots/mo) | €19/mo | $79/mo |
| Feature gating | None — all features on all plans | Some features locked to higher tiers |
| OG image generation | Dedicated /v1/og endpoint | Not available |
| PDF generation | Dedicated /v1/pdf endpoint | Via format parameter |
| Signed URLs | Yes | No |
| Custom JS/CSS injection | Yes | Yes |
| SDKs | JS, Python, Ruby, PHP, Go | JS, Python, PHP |
| Free tier | 100 screenshots/mo | 100 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 Volume | len.sh | ScreenshotOne | Savings |
|---|---|---|---|
| 100 (free tier) | €0 | $0 | — |
| 5,000 | €19 | $29 | 34% |
| 25,000 | €19 | $79 | 76% |
| 100,000 | €99 | $199 | 50% |
| 500,000 | €99 | Custom | Significant |
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:
- Build an HTML template for your OG images
- Host it somewhere accessible
- Screenshot that template URL at 1200x630
- 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:
| Parameter | Description |
|---|---|
paper_size | A4, Letter, Legal, Tabloid, Ledger |
landscape | Landscape orientation |
margin_top/right/bottom/left | CSS margin values |
print_background | Include background colors/images |
page_ranges | Specific pages, e.g. “1-3, 5” |
header_template | Custom HTML header |
footer_template | Custom HTML footer |
scale | Scale 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:
- Base URL:
api.screenshotone.com/taketoapi.len.sh/v1/screenshot - Parameter names:
viewport_widthtowidth,viewport_heighttoheight - 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.