Skip to content

Embed: Private pages and proxy

Most embeds are for public pages. The hard cases are private dashboards, region-locked products, and sites that block headless browsers. Both require
PRO
features — headers, x-api-header-*, and proxy — plus extra discipline because embed URLs are public by nature.
The shared patterns live in private pages patterns and proxy patterns. This page covers the embed-specific setup.

The embed URL is public

Whatever you put in an embed URL ends up in HTML, browser devtools, server logs, social previews, and downstream caches. That changes the threat model:
ApproachSafe in public embed URLs?
headers={ 'accept-language': 'es-ES' } (locale-only)Yes — value is non-sensitive
headers={ 'cookie': '...' } (sensitive)No — exposes credentials
x-api-header-cookie: ... request headerYes — sent server-to-server, never in the embed URL
proxy=https://user:pass@... query paramNo — leaks proxy credentials
proxy set inside an MQL call from the serverYes — never reaches the browser
The rule: only non-sensitive values belong in the embed URL. Everything else stays server-side, behind a backend that proxies the call to Microlink.

Embed an authenticated dashboard
PRO

Use x-api-header-* to forward the cookie or bearer token without exposing it:
curl -G https://pro.microlink.io \
  -d url=https://app.example.com/dashboard \
  -d screenshot=true \
  -d embed=screenshot.url \
  -d meta=false \
  -H 'x-api-key: YOUR_API_TOKEN' \
  -H 'x-api-header-cookie: session=abc123'
The response is the screenshot bytes — usable as an <img src> in an internal portal or a logged-in user's email. The cookie never reaches the browser because the request lives on your backend.
For non-sensitive values like locale or accept-language, the URL-based headers parameter is fine:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://example.com/pricing' URL with 'screenshot', 'meta', 'headers' & 'embed' API parameters:

CLI Microlink API example

microlink https://example.com/pricing&screenshot&headers.Accept-Language=es-ES&embed=screenshot.url

cURL Microlink API example

curl -G "https://api.microlink.io" \
  -d "url=https://example.com/pricing" \
  -d "screenshot=true" \
  -d "meta=false" \
  -d "headers.Accept-Language=es-ES" \
  -d "embed=screenshot.url"

JavaScript Microlink API example

import mql from '@microlink/mql'

const { data } = await mql('https://example.com/pricing', {
  screenshot: true,
  meta: false,
  headers: {
    "Accept-Language": "es-ES"
  },
  embed: "screenshot.url"
})

Python Microlink API example

import requests

url = "https://api.microlink.io/"

querystring = {
    "url": "https://example.com/pricing",
    "screenshot": "true",
    "meta": "false",
    "headers.Accept-Language": "es-ES",
    "embed": "screenshot.url"
}

response = requests.get(url, params=querystring)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://api.microlink.io/"

params = {
  url: "https://example.com/pricing",
  screenshot: "true",
  meta: "false",
  headers.Accept-Language: "es-ES",
  embed: "screenshot.url"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://api.microlink.io/";

$params = [
    "url" => "https://example.com/pricing",
    "screenshot" => "true",
    "meta" => "false",
    "headers.Accept-Language" => "es-ES",
    "embed" => "screenshot.url"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET"
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://api.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://example.com/pricing")
    q.Set("screenshot", "true")
    q.Set("meta", "false")
    q.Set("headers.Accept-Language", "es-ES")
    q.Set("embed", "screenshot.url")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
The locale-aware capture is rendered for an embedding without leaking secrets.

Front public embeds with a proxy server

When the embed URL must be visible (a public OG image, a marketing card) but the request needs a key or cookies, run your own proxy:
Your public URL becomes https://my-app.example.com/og?slug=hello. The proxy converts that to a Microlink call with the API key and any x-api-header-* it needs, then streams the response back. The browser never sees the credentials.
For the architecture in detail, see private pages patterns.

Embed sites blocked by antibot systems
PRO

If the target page returns a CAPTCHA, an antibot challenge, or EPROXYNEEDED, you need a proxy.
Pro plans include automatic proxy resolution — the API detects the antibot provider and routes through the right pool without you setting proxy at all:

The following examples show how to use the Microlink API with CLI, cURL, JavaScript, Python, Ruby, PHP & Golang, targeting 'https://difficult-target.example' URL with 'screenshot', 'meta' & 'embed' API parameters:

CLI Microlink API example

microlink https://difficult-target.example&screenshot&embed=screenshot.url

cURL Microlink API example

curl -G "https://api.microlink.io" \
  -d "url=https://difficult-target.example" \
  -d "screenshot=true" \
  -d "meta=false" \
  -d "embed=screenshot.url"

JavaScript Microlink API example

import mql from '@microlink/mql'

const { data } = await mql('https://difficult-target.example', {
  screenshot: true,
  meta: false,
  embed: "screenshot.url"
})

Python Microlink API example

import requests

url = "https://api.microlink.io/"

querystring = {
    "url": "https://difficult-target.example",
    "screenshot": "true",
    "meta": "false",
    "embed": "screenshot.url"
}

response = requests.get(url, params=querystring)

print(response.json())

Ruby Microlink API example

require 'uri'
require 'net/http'

base_url = "https://api.microlink.io/"

params = {
  url: "https://difficult-target.example",
  screenshot: "true",
  meta: "false",
  embed: "screenshot.url"
}

uri = URI(base_url)
uri.query = URI.encode_www_form(params)

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Get.new(uri)
response = http.request(request)

puts response.body

PHP Microlink API example

<?php

$baseUrl = "https://api.microlink.io/";

$params = [
    "url" => "https://difficult-target.example",
    "screenshot" => "true",
    "meta" => "false",
    "embed" => "screenshot.url"
];

$query = http_build_query($params);
$url = $baseUrl . '?' . $query;

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => $url,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => "",
    CURLOPT_MAXREDIRS => 10,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    CURLOPT_CUSTOMREQUEST => "GET"
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #: " . $err;
} else {
    echo $response;
}

Golang Microlink API example

package main

import (
    "fmt"
    "net/http"
    "net/url"
    "io"
)

func main() {
    baseURL := "https://api.microlink.io"

    u, err := url.Parse(baseURL)
    if err != nil {
        panic(err)
    }
    q := u.Query()
    q.Set("url", "https://difficult-target.example")
    q.Set("screenshot", "true")
    q.Set("meta", "false")
    q.Set("embed", "screenshot.url")
    u.RawQuery = q.Encode()

    req, err := http.NewRequest("GET", u.String(), nil)
    if err != nil {
        panic(err)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, err := io.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    fmt.Println(string(body))
}
On a Pro plan, the API quietly resolves the antibot when it detects one. The x-fetch-mode response header will be prefixed with proxy-.
You can also pass your own proxy URL when you have a residential or country-specific service:
import mql from '@microlink/mql'

const { data } = await mql('https://example.com/pricing', {
  proxy: process.env.PROXY_URL
})

return data.screenshot.url // hand this to your embed renderer
Keep that call server-side — never expose the proxy= param in a public embed URL.
For the full proxy reference (when, how, and how to verify), see proxy patterns.

Geofenced content

A common embed use case: a product whose homepage shows different prices per country, and you want each region's preview cached separately. Combine proxy (for the IP region) with a content-hash cache buster (so each region cache is independent):
async function regionalEmbed (region) {
  const proxy = PROXIES[region] // e.g. 'https://user:[email protected]:8080'
  const { data } = await mql('https://example.com/pricing', {
    proxy,
    screenshot: true,
    meta: false
  })
  return data.screenshot.url
}
Render the resulting URL into your <meta property="og:image"> for that locale. Pair with ttl: '1d' and staleTtl: 0 so each region's variant stays warm. See geolocation patterns.

When private embeds still fail

If x-api-header-* and proxy are both set and the embed still returns wrong content:
  • Confirm the cookie / token has not expired. Authenticated previews are only as fresh as the credential.
  • Check x-fetch-mode — anything other than proxy-* means the proxy did not engage.
  • Look for EPROXYNEEDED or EAUTH in the response body. See error codes.
For the rest, continue to troubleshooting.

Next step

For debugging missing iframes, broken images, and provider-specific quirks, see troubleshooting.