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:
| Approach | Safe 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 header | Yes — sent server-to-server, never in the embed URL |
proxy=https://user:pass@... query param | No — leaks proxy credentials |
proxy set inside an MQL call from the server | Yes — 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.urlcURL 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.bodyPHP 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))
}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"
})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:
- @microlink/proxyfor self-hosted Node.js / Express.
- @microlink/edge-proxyfor Cloudflare Workers and edge runtimes.
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.urlcURL 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.bodyPHP 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))
}import mql from '@microlink/mql'
const { data } = await mql('https://difficult-target.example', {
screenshot: true,
meta: false,
embed: "screenshot.url"
})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 rendererKeep 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 thanproxy-*means the proxy did not engage. - Look for
EPROXYNEEDEDorEAUTHin 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.