Microlink Alternative for Screenshots: Tested Switch

A Microlink alternative for screenshots: live Microlink timings, its 2x pixel-ratio default, a parameter map, pricing and migration code in 3 languages.

By Simon Chiu · Updated

Disclosure: I build RenderScreenshot. Microlink is a good API with a broader data focus than ours, and this page says when to keep it.

Microlink is a browser API for data about a URL (metadata, logos, palettes, Lighthouse, Markdown) that also takes screenshots. If screenshots are most of what you use it for, a dedicated screenshot API can be simpler: the image comes back in the first response, there are presets for common sizes, and the price fits low volumes better. This page shows what I measured on Microlink's free endpoint, one default that trips up most comparisons, a parameter map, prices at three volumes, and a drop-in function in Node, Python and Ruby. For the wider market, see the best screenshot API comparison.

Last verified: October 15, 2026, against Microlink's screenshot, embed, authentication, rate limit and cache docs, and its pricing page, as published on October 14 and 15, 2026.

The short answer

Choose Microlink if Choose RenderScreenshot if
You want metadata, logo, palette, Markdown or Lighthouse data with the screenshot You want the image bytes in the first response, without a second download
You need more than 60,000 requests a month on a self-serve plan You run 1,000 to 12,000 shots a month and want to pay for that, not for 46,000
You prefer a pause at 100% quota to any chance of overage You want presets (og_card, twitter_card_large, mobile, desktop_hd)
You want to test without signing up You need signed URLs for public HTML, or a cache purge API
You like a free keyless endpoint for prototypes You want SDKs in Python, Ruby, PHP, Java or Go

Microlink is the one vendor in our comparison I could test live without an account, so here are real numbers. Treat them as a small sample, not a benchmark.

Method box. October 15, 2026. Client: Apple M1 Pro, Node.js 22.18.0, network in Toronto, Canada. Endpoint: api.microlink.io (keyless). Target: https://example.com/ with a unique rsbench query parameter per run, so the first request could not be served from cache. Request: 1280×800, PNG, waitUntil=load, embed=screenshot.url. "Cold" is the first request for that unique URL; "warm" is the same request repeated at once. Time is measured to the last byte of the image.

Phase Runs Times Success Output
Cold 4 1,135, 757, 1,181, 1,006 ms 4 of 4 70,539 bytes, 1280×800 PNG
Warm 4 99, 49, 45, 51 ms 4 of 4 70,539 bytes

The warm numbers show Microlink's edge cache working: once a URL is cached, the response came back from a nearby edge in under 100 ms. Its cache docs describe a unified cache plus per-location edge caches, and the response carried x-cache-status: MISS on the first call.

The default that changes every size comparison. Without viewport.deviceScaleFactor=1, the same request returned a 2560×1600 PNG of 168,630 bytes. Microlink renders at a device scale factor of 2 unless you say otherwise. If you compare Microlink with any API that defaults to 1x (RenderScreenshot, ScreenshotOne, ApiFlash), set the factor explicitly on both sides, or you are comparing different images.

Not measured yet: RenderScreenshot under the same conditions. That needs our paid benchmark run, and I will add the row with the raw CSV when it is done, whichever way it goes. You can produce both rows yourself with the script in the best screenshot API comparison:

node compare.mjs --providers microlink,renderscreenshot --urls urls.txt --runs 3 --out microlink-vs-rs.csv

Parameter map

Microlink uses dot notation in the query string (viewport.width, screenshot.type). RenderScreenshot uses nested JSON on POST and flat names on GET.

Microlink RenderScreenshot (POST body) Notes
x-api-key header (pro) or no key (free) Authorization: Bearer rs_live_... Or api_key on GET, or a signed URL
url url
screenshot=true (always a screenshot)
embed=screenshot.url (default: image bytes)
screenshot.type (png, jpeg) output.format (png, jpeg, webp, avif, pdf, html)
screenshot.quality output.quality
screenshot.fullPage capture.mode: "full_page"
screenshot.element capture.mode: "element", capture.selector
screenshot.omitBackground output.transparent
screenshot.overlay, screenshot.codeScheme, screenshot.animated - Browser-frame overlays, code themes and animated captures are Microlink-only
viewport.width, viewport.height viewport.width, viewport.height
viewport.deviceScaleFactor (default 2) viewport.scale (default 1, max 3) Set it on both sides
device viewport.device or a preset
colorScheme browser.dark_mode
mediaType browser.media
waitUntil wait.until (load, domcontentloaded, networkidle)
waitForSelector wait.for_selector
waitForTimeout wait.delay (ms)
timeout wait.timeout (seconds, max 60)
adblock block.ads, block.cookie_banners Two separate switches on our side
click page.click
scripts, styles page.scripts, page.styles
headers network.headers
proxy network.proxy
ttl, force, cacheKey cache.ttl, cache.refresh, cache.key
filename output.filename
meta (metadata fields) metadata.include Ours: title, description, favicon, Open Graph, Twitter card, fonts, size, status, headers
pdf output.format: "pdf" + pdf options
data, function, insights, palette, audio, video - Microlink's data features

The response shape is the real migration

Microlink's default answer is JSON:

{ "status": "success", "data": { "screenshot": { "url": "https://...", "type": "png", "size": 70539 } } }

Your code then downloads the file from Microlink's CDN. RenderScreenshot's default answer is the image itself, with metadata in headers: X-Cache-URL (a public CDN link to the same image, no key in it), X-Cache-Hit, X-Screenshot-Width, X-Screenshot-Height and X-Credits-Used (response headers).

If your code stores the Microlink CDN URL and shows it later, you have two choices on our side. Store X-Cache-URL, which is public until the cache TTL expires (default 24 hours, up to 30 days with cache.ttl). Or store the bytes yourself, or let us upload them to your bucket with the storage options.

The adapter below hides the difference: both providers return { image, publicUrl }.

Migration code

Raw HTTP on both sides, so you can read every parameter. With RS_ROLLOUT_PERCENT unset, it calls Microlink; at 100, RenderScreenshot.

Node.js

// screenshot.mjs: Microlink and RenderScreenshot behind one function, raw HTTP on both sides.
// Microlink answers with JSON that points at a CDN file; RenderScreenshot answers with the image
// and puts a public CDN link in the X-Cache-URL header. Both become { image, publicUrl }.
// Node 22+, no dependencies.
import { writeFile } from 'node:fs/promises';

const USE_RS = process.env.RS_ROLLOUT_PERCENT === '100';

async function viaMicrolink({ url, width, height, fullPage }) {
  const key = process.env.MICROLINK_API_KEY; // optional: without it you get the small keyless quota
  const q = new URLSearchParams({
    url,
    screenshot: 'true',
    'viewport.width': String(width),
    'viewport.height': String(height),
    'viewport.deviceScaleFactor': '1', // Microlink renders at 2x unless you ask for 1x
    'screenshot.type': 'png',
    'screenshot.fullPage': String(fullPage),
  });
  const host = key ? 'https://pro.microlink.io' : 'https://api.microlink.io';
  const res = await fetch(`${host}/?${q}`, { headers: key ? { 'x-api-key': key } : {}, signal: AbortSignal.timeout(60_000) });
  const body = await res.json();
  if (!res.ok || body.status !== 'success') throw new Error(`Microlink ${res.status}: ${body.message ?? body.code}`);
  const file = await fetch(body.data.screenshot.url);
  if (!file.ok) throw new Error(`Microlink CDN ${file.status}`);
  return { image: Buffer.from(await file.arrayBuffer()), publicUrl: body.data.screenshot.url };
}

async function viaRenderScreenshot({ url, width, height, fullPage }) {
  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({
      url,
      viewport: { width, height, scale: 1 },
      capture: { mode: fullPage ? 'full_page' : 'viewport' },
      output: { format: 'png' },
    }),
    signal: AbortSignal.timeout(60_000),
  });
  if (!res.ok) throw new Error(`RenderScreenshot ${res.status}: ${await res.text()}`);
  return { image: Buffer.from(await res.arrayBuffer()), publicUrl: res.headers.get('x-cache-url') };
}

export function screenshot(job) {
  const full = { width: 1280, height: 800, fullPage: false, ...job };
  return USE_RS ? viaRenderScreenshot(full) : viaMicrolink(full);
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const { image, publicUrl } = await screenshot({ url: process.argv[2] ?? 'https://example.com/' });
  await writeFile('out.png', image);
  console.log(`out.png: ${image.length} bytes, public URL: ${publicUrl ? 'yes' : 'no'}`);
}

Python

# screenshot.py: Microlink and RenderScreenshot behind one function; returns (image_bytes, public_url).
# Python 3.9+. pip install httpx
import os
import sys

import httpx

USE_RS = os.environ.get('RS_ROLLOUT_PERCENT') == '100'


def via_microlink(url, width=1280, height=800, full_page=False):
    key = os.environ.get('MICROLINK_API_KEY')
    params = {
        'url': url,
        'screenshot': 'true',
        'viewport.width': width,
        'viewport.height': height,
        'viewport.deviceScaleFactor': 1,
        'screenshot.type': 'png',
        'screenshot.fullPage': str(full_page).lower(),
    }
    host = 'https://pro.microlink.io' if key else 'https://api.microlink.io'
    res = httpx.get(host + '/', params=params, headers={'x-api-key': key} if key else {}, timeout=60)
    body = res.json()
    if res.status_code != 200 or body.get('status') != 'success':
        raise RuntimeError(f"Microlink {res.status_code}: {body.get('message') or body.get('code')}")
    public_url = body['data']['screenshot']['url']
    image = httpx.get(public_url, timeout=60)
    image.raise_for_status()
    return image.content, public_url


def via_renderscreenshot(url, width=1280, height=800, full_page=False):
    res = httpx.post(
        'https://api.renderscreenshot.com/v1/screenshot',
        headers={'Authorization': f"Bearer {os.environ['RS_API_KEY']}"},
        json={
            'url': url,
            'viewport': {'width': width, 'height': height, 'scale': 1},
            'capture': {'mode': 'full_page' if full_page else 'viewport'},
            'output': {'format': 'png'},
        },
        timeout=60,
    )
    res.raise_for_status()
    return res.content, res.headers.get('x-cache-url')


def screenshot(url, **kwargs):
    return (via_renderscreenshot if USE_RS else via_microlink)(url, **kwargs)


if __name__ == '__main__':
    image, public_url = screenshot(sys.argv[1] if len(sys.argv) > 1 else 'https://example.com/')
    with open('out.png', 'wb') as f:
        f.write(image)
    print(f"out.png: {len(image)} bytes, public URL: {'yes' if public_url else 'no'}")

Ruby

# screenshot.rb: Microlink and RenderScreenshot behind one method; returns [image_bytes, public_url].
# Ruby 3.0+, standard library only.
require 'json'
require 'net/http'

USE_RS = ENV['RS_ROLLOUT_PERCENT'] == '100'

def via_microlink(url, width: 1280, height: 800, full_page: false)
  key = ENV['MICROLINK_API_KEY']
  uri = URI(key ? 'https://pro.microlink.io/' : 'https://api.microlink.io/')
  uri.query = URI.encode_www_form(
    'url' => url, 'screenshot' => 'true', 'viewport.width' => width, 'viewport.height' => height,
    'viewport.deviceScaleFactor' => 1, 'screenshot.type' => 'png', 'screenshot.fullPage' => full_page
  )
  req = Net::HTTP::Get.new(uri)
  req['x-api-key'] = key if key
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') { |http| http.request(req) }
  body = JSON.parse(res.body)
  raise "Microlink #{res.code}: #{body['message'] || body['code']}" unless body['status'] == 'success'

  public_url = body.dig('data', 'screenshot', 'url')
  [Net::HTTP.get(URI(public_url)), public_url]
end

def via_renderscreenshot(url, width: 1280, height: 800, full_page: false)
  uri = URI('https://api.renderscreenshot.com/v1/screenshot')
  req = Net::HTTP::Post.new(uri, 'Authorization' => "Bearer #{ENV.fetch('RS_API_KEY')}", 'Content-Type' => 'application/json')
  req.body = {
    url: url,
    viewport: { width: width, height: height, scale: 1 },
    capture: { mode: full_page ? 'full_page' : 'viewport' },
    output: { format: 'png' }
  }.to_json
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', read_timeout: 60) { |http| http.request(req) }
  raise "RenderScreenshot #{res.code}: #{res.body}" unless res.is_a?(Net::HTTPSuccess)

  [res.body, res['x-cache-url']]
end

def screenshot(url, **kwargs)
  USE_RS ? via_renderscreenshot(url, **kwargs) : via_microlink(url, **kwargs)
end

if $PROGRAM_NAME == __FILE__
  image, public_url = screenshot(ARGV[0] || 'https://example.com/')
  File.binwrite('out.png', image)
  puts "out.png: #{image.bytesize} bytes, public URL: #{public_url ? 'yes' : 'no'}"
end

Tested on October 15, 2026. The Microlink path ran live against api.microlink.io in all three languages (Node.js 22.18.0, Python 3.14 with httpx 0.28.1, Ruby 4.0.7) and wrote a 1280×800 PNG each time. The RenderScreenshot path ran against a local mock that checks the endpoint, Bearer auth and JSON body. If you prefer an SDK on our side, the same request is client.take(TakeOptions.url(url).width(1280).height(800).scale(1).format('png')) in the Node SDK.

Price at three volumes

As listed on each pricing page on October 14, 2026:

Monthly volume Microlink RenderScreenshot
Free 100 requests a month (keyless endpoint: soft limit of 25) 150 credits a month, no credit card
1,000 €39 (Pro, 46,000 requests) $10 (Starter, 2,500 credits)
10,000 €39 (Pro, 46,000 requests) $40 (Growth, 12,000 credits)
100,000 €120 (Pro, 140,000 requests) No self-serve plan; Scale is $100 for 60,000

Microlink's EUR prices come from the structured price data on its pricing page; the page displayed $49 for the first Pro tier to me. Microlink counts every API call as a request, whichever endpoint, and does not count cache hits. At 100% of quota it pauses requests until the next cycle or an upgrade, with no overage fees. The pricing comparison has the other vendors.

  • You use the data features. Metadata, logos, palettes, Markdown, technology detection and Lighthouse in the same call have no equivalent on our side beyond basic metadata.
  • You are above 60,000 requests a month and want a self-serve plan.
  • You want the keyless endpoint for prototypes, demos or docs examples.
  • You want hard caps. Microlink pauses at quota instead of billing more; we stop at your credit balance too, so this is a tie if that is your only concern.
  • You use browser-frame overlays or animated screenshots.

Try it on your URLs

Create a free RenderScreenshot key and run the adapter above with RS_ROLLOUT_PERCENT=100 on the same ten URLs you use with Microlink. Set the scale to 1 on both, and compare the images, not only the times.

Frequently asked questions

Why are my Microlink screenshots twice the size I asked for?
Microlink renders at a device scale factor of 2 by default. In our test, a 1280x800 request returned a 2560x1600 PNG of 168,630 bytes; adding viewport.deviceScaleFactor=1 returned 1280x800 at 70,539 bytes.
Does Microlink return the image or JSON?
JSON by default, with the image at data.screenshot.url on Microlink's CDN. Add embed=screenshot.url to get the image bytes directly. RenderScreenshot returns the image by default and puts a public CDN link in the X-Cache-URL header.
Can I use Microlink without an API key?
Yes, for small tests: the keyless endpoint api.microlink.io has a soft limit of 25 requests (per Microlink's rate-limit docs). Production use goes through pro.microlink.io with an x-api-key header.
Is Microlink cheaper than RenderScreenshot?
At 100,000 requests a month, yes: €120 for 140,000 on Microlink Pro versus no self-serve plan on RenderScreenshot. At 1,000 a month RenderScreenshot is cheaper ($10 vs €39), and at 10,000 the two are close ($40 vs €39).

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.