Disclosure: I build RenderScreenshot, a screenshot API that competes with both. For this page I measured Microlink's free endpoint, which needs no key. I did not have a ScreenshotOne account for testing, so its side comes from its documentation and pricing page (read 2026-10-14) and the script below is untested against it. I also compare against ScreenshotOne's own claims only where its docs state them.
Short answer: the two products start from opposite ends. ScreenshotOne is a rendering API: you send a URL or HTML and get back image bytes or a PDF, with a long list of options for waiting, blocking, viewport and output. Microlink is a URL-to-data API: you send a URL and get back JSON about the page (title, description, images, logo), and a screenshot is one field you can ask for. Pick Microlink when you need link-card data and an occasional screenshot. Pick ScreenshotOne when the image is the product.
This page is part of our screenshot API comparisons. For Microlink against another rendering API, see Microlink vs Urlbox.
What one request gives you
| ScreenshotOne | Microlink | |
|---|---|---|
| Request | GET or POST https://api.screenshotone.com/take?url=... |
GET https://api.microlink.io/?url=...&screenshot=true (free) or https://pro.microlink.io (paid) |
| Auth | access_key parameter or X-Access-Key header; signed links for public URLs |
None on the free endpoint; x-api-key header on Pro |
| Default response | The image bytes (response_type=by_format) |
JSON: data.screenshot.url, size, type, width, height, plus metadata |
| Image bytes in one call | Default | Add embed=screenshot.url: the response is the image itself |
| Default output in our test | JPEG by default (per docs) | PNG, 2560x1600 for a 1280x800 viewport |
| Metadata | Opt-in metadata_* options (title, Open Graph, fonts, icon, content, status) |
On by default (meta=true); set meta=false for screenshots only |
| Cache | Off until cache=true; 4-hour default TTL, up to one month |
On; ttl defaults to 24 hours; force=true skips the cache |
| HTML and Markdown input | Yes | No (URL only) |
Yes (format=pdf) |
Yes (pdf parameter) |
|
| Async + webhooks, S3 upload | Yes | Not part of the screenshot parameters we read |
Sources: ScreenshotOne options, ScreenshotOne caching, Microlink screenshot parameter, Microlink embed, Microlink authentication.
What we measured on Microlink
Tested on 2026-10-15 with Node 22.18.0 from one client machine, against https://example.com, using Microlink's free endpoint. Microlink served these requests from its iad region (from the response's request ID). Sample sizes are small because the free endpoint has a small quota, so treat the numbers as indicative.
| Request | Runs | API response | Total incl. image download | Output |
|---|---|---|---|---|
Defaults, force=true (fresh render) |
3 | 1,049 to 1,210 ms | 1,202 to 1,371 ms | PNG 2560x1600, 168.6 KB |
Defaults, cached (x-cache-status: HIT) |
2 | 50 to 168 ms | 88 to 227 ms | Same file |
1280x800 at scale 1, meta=false, fresh |
1 | 1,334 ms | PNG 1280x800, 70.5 KB | |
| Same, cached | 2 | 85 to 249 ms | 70.5 KB, then 54.5 KB | |
Defaults with embed=screenshot.url (cached) |
1 | 503 ms | Same call | Image bytes, content-type: image/png |
Four things stood out:
- The default screenshot is 2x. With only
urlandscreenshot=true, we got 2560x1600. That is good for retina thumbnails and wasteful for anything that gets resized down. Setviewport.deviceScaleFactor=1when you want 1:1 pixels; it cut the file from 168.6 KB to 70.5 KB here. - The default path is two requests. The JSON response contains a URL on Microlink's CDN, and you fetch the image separately. On fresh renders, the second request added 140 to 220 ms.
embed=screenshot.urlreturns the bytes directly in one call. - The cache is fast and on by default. Cached responses came back in 50 to 250 ms. If you need a new render (for example after a deploy), pass
force=true. - Compare the images, not only the status. One cached response for the 1280x800 request returned a 54.5 KB file where the fresh render had been 70.5 KB. We did not investigate why. When you test any cached API, check what is inside the file.
The anonymous endpoint also returned x-rate-limit-limit: 25 with a reset time about a day ahead, while the pricing page lists the free plan as 100 requests a month. Plan for the lower of the two when you prototype.
We did not measure ScreenshotOne. Its docs state that a request without cache=true renders every time and returns the bytes directly, so its default path is one request with no cache in front.
Price at three volumes
List prices on 2026-10-14 (ScreenshotOne) and 2026-10-15 (Microlink), monthly billing:
| Requests per month | ScreenshotOne | Microlink |
|---|---|---|
| 1,000 | Basic, $17 (2,000) | Pro, $49 (46,000); the free plan covers 100 |
| 10,000 | Growth, $79 (10,000) | Pro, $49 (46,000) |
| 100,000 | Scale, $259 (50,000) + 50,000 at $0.004 = $459 | A larger Pro tier (the page offers 84K, 140K, 280K and 420K; prices for those tiers were not in our capture) |
Counting rules differ, which matters more than list price:
- ScreenshotOne counts successful renders that were not served from cache. Failed renders do not count.
- Microlink counts requests. Its Pro price is about $1.07 per 1,000 requests at the 46,000 tier, and one Pro key covers every Microlink product (metadata, screenshots, PDFs, Markdown and more).
Up to about 5,500 screenshots a month, ScreenshotOne's Basic plan costs less, even with extra screenshots beyond its 2,000 at $0.009 each ($17 + 3,500 x $0.009 is about $49). From there up to 46,000, Microlink's Pro plan is cheaper, as long as its screenshot options cover your pages.
Probe both with the same settings
This script times fresh and cached screenshots on either service with matched viewport and scale. The Microlink part ran against the free endpoint to produce the second half of the table above. The ScreenshotOne part is untested against a live key.
// Time uncached and cached screenshots on Microlink and ScreenshotOne. // Usage: node probe.mjs <microlink|screenshotone> [url] [runs] // Env: MICROLINK_API_KEY (optional: without it, the free endpoint is used), SCREENSHOTONE_ACCESS_KEY const [provider = 'microlink', target = 'https://example.com', runsArg = '3'] = process.argv.slice(2); const RUNS = Number(runsArg); async function microlink(fresh) { const pro = Boolean(process.env.MICROLINK_API_KEY); const u = new URL(pro ? 'https://pro.microlink.io/' : 'https://api.microlink.io/'); u.search = new URLSearchParams({ url: target, screenshot: 'true', meta: 'false', // skip metadata extraction you do not need 'viewport.width': '1280', 'viewport.height': '800', 'viewport.deviceScaleFactor': '1', ...(fresh ? { force: 'true' } : {}), }); const res = await fetch(u, { headers: pro ? { 'x-api-key': process.env.MICROLINK_API_KEY } : {}, signal: AbortSignal.timeout(60_000) }); const body = await res.json(); if (body.status !== 'success') throw new Error(`HTTP ${res.status}: ${JSON.stringify(body).slice(0, 200)}`); const img = await fetch(body.data.screenshot.url, { signal: AbortSignal.timeout(60_000) }); // second request return { bytes: (await img.arrayBuffer()).byteLength, cache: res.headers.get('x-cache-status') }; } async function screenshotone(fresh) { const u = new URL('https://api.screenshotone.com/take'); u.search = new URLSearchParams({ url: target, format: 'png', viewport_width: '1280', viewport_height: '800', device_scale_factor: '1', ...(fresh ? {} : { cache: 'true', cache_ttl: '14400' }), }); const res = await fetch(u, { headers: { 'X-Access-Key': process.env.SCREENSHOTONE_ACCESS_KEY }, signal: AbortSignal.timeout(90_000) }); if (!res.ok) throw new Error(`HTTP ${res.status}: ${(await res.text()).slice(0, 200)}`); return { bytes: (await res.arrayBuffer()).byteLength, cache: fresh ? 'none' : 'requested' }; } const run = { microlink, screenshotone }[provider]; if (!run) throw new Error(`unknown provider ${provider}`); for (const fresh of [...Array(RUNS).fill(true), false, false]) { const t0 = performance.now(); try { const r = await run(fresh); console.log(JSON.stringify({ provider, fresh, ms: Math.round(performance.now() - t0), ...r })); } catch (err) { console.error(`${provider}: ${err.message}`); process.exitCode = 1; } }
With ScreenshotOne, the first cached request renders and stores, and only the second is a cache hit, which is why the script makes two cached calls. Each fresh call counts against your plan on both services.
Moving between them
If you are on Microlink and only use the screenshot field, the main change is the response shape. Code that reads data.screenshot.url from JSON has to handle bytes instead, or you set ScreenshotOne's response_type=json. ScreenshotOne's docs say the URL in that JSON is temporary, so copy files you want to keep into your own storage.
If you use Microlink's metadata (title, description, logo, image) for link cards, a rendering API does not replace it one to one. ScreenshotOne's metadata_* options return some of those fields alongside a render; check that they cover what your cards read before you switch. Many teams keep Microlink for metadata and move only the rendering, which is a reasonable split. Our roundup of link preview APIs covers that use case in more depth.
When each is the better choice
Microlink fits when:
- You build link previews and need page metadata and a picture in one call.
- You want to prototype without signing up (the free endpoint needs no key).
- Your volume sits between about 5,500 and 46,000 requests a month, or you can use the Pro plan across several Microlink products.
- Default 2x screenshots suit your UI.
ScreenshotOne fits when:
- The image or PDF is the deliverable and must look right on difficult pages: waits, blocking, scripts, styles, clicks.
- You render HTML or Markdown, not only URLs.
- You need async jobs with webhooks, or upload straight to your S3 bucket.
- You need signed links so a public
<img src>cannot be reused with other parameters.
A third option for rendered previews
If you want the rendering-API model with a public CDN copy of every result, RenderScreenshot returns the image bytes and an X-Cache-URL header that needs no auth, and the link_preview preset sets an 800x600 viewport for chat and link cards (presets):
curl -o preview.png -D headers.txt \ -H "Authorization: Bearer $RS_API_KEY" \ "https://api.renderscreenshot.com/v1/screenshot?url=https%3A%2F%2Fexample.com&preset=link_preview" grep -i x-cache-url headers.txt
Get a free API key and add it as a third provider in the probe script to compare all three on your URLs.
Last verified: 2026-10-15. Microlink measured on its free endpoint (small sample); ScreenshotOne not measured.