ScreenshotOne vs Microlink for Screenshots

ScreenshotOne vs Microlink: rendering API vs URL-to-data API, measured Microlink cache timings, default resolution, two-step responses, prices at three volumes.

By Simon Chiu · Updated

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)
PDF 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.

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:

  1. The default screenshot is 2x. With only url and screenshot=true, we got 2560x1600. That is good for retina thumbnails and wasteful for anything that gets resized down. Set viewport.deviceScaleFactor=1 when you want 1:1 pixels; it cut the file from 168.6 KB to 70.5 KB here.
  2. 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.url returns the bytes directly in one call.
  3. 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.
  4. 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.

Frequently asked questions

Is Microlink a screenshot API?
It is a URL-to-data API with a screenshot option. A screenshot request returns JSON with a hosted image URL, alongside page metadata unless you turn metadata off. ScreenshotOne returns the image itself by default.
Why are Microlink screenshots 2560x1600?
In our test, a request with only url and screenshot=true returned a 2560x1600 PNG, which is a 1280x800 viewport at device scale factor 2. Set viewport.deviceScaleFactor=1 if you want 1280x800 output.
Can I use Microlink without an API key?
Yes. The free endpoint, api.microlink.io, worked without a key in our test. Microlink's pricing page lists the free plan at 100 requests a month; paid plans use pro.microlink.io with an x-api-key header.
Which one is better for link previews?
Microlink, if you need title, description, logo and image for a link card in one call. ScreenshotOne, if the preview must be a rendered picture of the page with control over waiting, blocking and format.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.