ScreenshotNeo

BlogHow-to

How to Save Microlink Screenshots to Amazon S3

Capture a page with Microlink, fetch its screenshot asset, and upload the bytes to your S3 bucket with runnable examples and troubleshooting tips.

By the ScreenshotNeo team4 October 202610 min read

Short answer: Microlink generates the screenshot and returns an asset URL. Your application must fetch the image bytes from that URL and upload them to your own Amazon S3 bucket; Microlink’s documented flow does not upload directly to your bucket. The flow is capture → fetch asset → PutObject.

This guide uses a server-side Node.js example with Microlink’s screenshot API and the AWS SDK for JavaScript v3. It also shows the equivalent transfer steps with Python and cURL. Keep AWS credentials in a trusted server-side environment. See Microlink’s screenshot parameter documentation and AWS’s PutObject examples.

1. Understand the two-service workflow

  1. Send Microlink a request for the page and enable its screenshot parameter.
  2. Read the screenshot object in the response. Microlink’s example includes an asset URL and image metadata such as dimensions, type, and size.
  3. Fetch that asset URL from your server and read the response body as bytes.
  4. Upload those bytes to S3 with a bucket name, object key, body, and the image’s actual content type.

The fetch between the two services is application code: Microlink returns an asset URL, while S3’s upload operation accepts the object data as its body. Don’t assume Microlink’s asset URL is a permanent S3 URL or that Microlink writes into your bucket.

  • Choose an S3 bucket in the account and region where the object should live.
  • Choose a key, for example captures/example-com/homepage.jpg. A stable key replaces the object at that key when uploaded again; a unique key preserves separate captures.
  • Configure AWS credentials for the process using your normal trusted server environment or AWS credential provider. Do not put privileged credentials in browser code or public URLs.
  • Grant only the permissions needed for the bucket and upload workflow. The exact policy depends on your account and configuration; the AWS PutObject example documents the operation, not a policy tailored to every deployment.
  • Decide how the object should be accessed: private, through an application, or through a controlled CDN path. Set access and retention deliberately.

Microlink’s API options, quotas, and plans can change. Check its current API page and the screenshot parameter docs for the options and availability you need.

3. Runnable Node.js example

This example requests a screenshot, retrieves the returned URL, and uploads the bytes with AWS SDK for JavaScript v3. It expects Node.js with built-in fetch (Node.js 18 or newer), and the AWS SDK packages installed in the project:

npm install @aws-sdk/client-s3

Set AWS_REGION, S3_BUCKET, MICROLINK_TARGET_URL, and any Microlink authentication required by your account in the server environment. The sample uses the common Microlink API response shape data.screenshot.url; confirm the current response shape for the API options you use.

import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

const targetUrl = process.env.MICROLINK_TARGET_URL;
const bucket = process.env.S3_BUCKET;
const region = process.env.AWS_REGION;

if (!targetUrl || !bucket || !region) {
  throw new Error("Set MICROLINK_TARGET_URL, S3_BUCKET, and AWS_REGION");
}

const s3 = new S3Client({ region });
const key = `captures/${new URL(targetUrl).hostname}/${Date.now()}.jpg`;

async function saveScreenshot() {
  const apiUrl = new URL("https://api.microlink.io");
  apiUrl.searchParams.set("url", targetUrl);
  apiUrl.searchParams.set("screenshot", "true");

  const captureResponse = await fetch(apiUrl);
  if (!captureResponse.ok) {
    throw new Error(`Microlink request failed: HTTP ${captureResponse.status}`);
  }

  const capture = await captureResponse.json();
  const screenshotUrl = capture?.data?.screenshot?.url;
  if (!screenshotUrl) {
    throw new Error("Microlink response did not contain data.screenshot.url");
  }

  const imageResponse = await fetch(screenshotUrl);
  if (!imageResponse.ok) {
    throw new Error(`Screenshot asset fetch failed: HTTP ${imageResponse.status}`);
  }

  const imageBytes = new Uint8Array(await imageResponse.arrayBuffer());
  if (imageBytes.byteLength === 0) {
    throw new Error("Screenshot asset was empty");
  }

  const contentType = imageResponse.headers.get("content-type")?.split(";")[0]
    || capture?.data?.screenshot?.type
    || "image/jpeg";

  await s3.send(new PutObjectCommand({
    Bucket: bucket,
    Key: key,
    Body: imageBytes,
    ContentType: contentType
  }));

  console.log(`Uploaded s3://${bucket}/${key} (${contentType}, ${imageBytes.byteLength} bytes)`);
}

saveScreenshot().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

If your account requires a Microlink API key or other request authentication, add it according to Microlink’s current documentation and keep the secret in the server environment. Do not hard-code it in a client application.

4. Python transfer example

The following shows the same sequence with Python. Install requests and boto3, and configure AWS credentials and region through your normal server environment or AWS credential provider. The response path shown follows the Microlink screenshot response example; verify it against the options you use.

python -m pip install requests boto3
import os
from urllib.parse import urlparse
import requests
import boto3

TARGET_URL = os.environ["MICROLINK_TARGET_URL"]
BUCKET = os.environ["S3_BUCKET"]
REGION = os.environ["AWS_REGION"]

response = requests.get(
    "https://api.microlink.io",
    params={"url": TARGET_URL, "screenshot": "true"},
    timeout=90,
)
response.raise_for_status()
capture = response.json()
screenshot_url = capture.get("data", {}).get("screenshot", {}).get("url")
if not screenshot_url:
    raise RuntimeError("Microlink response did not contain data.screenshot.url")

image_response = requests.get(screenshot_url, timeout=90)
image_response.raise_for_status()
image_bytes = image_response.content
if not image_bytes:
    raise RuntimeError("Screenshot asset was empty")

content_type = image_response.headers.get("Content-Type", "image/jpeg").split(";")[0]
host = urlparse(TARGET_URL).hostname or "page"
key = f"captures/{host}/{int(__import__('time').time())}.jpg"

s3 = boto3.client("s3", region_name=REGION)
s3.put_object(
    Bucket=BUCKET,
    Key=key,
    Body=image_bytes,
    ContentType=content_type,
)
print(f"Uploaded s3://{BUCKET}/{key} ({content_type}, {len(image_bytes)} bytes)")

For production code, make the file extension agree with the actual returned image type, or use a key without a misleading extension. If Microlink returns a different response shape for your chosen parameters, adjust the extraction rather than uploading a guessed URL.

5. cURL capture and S3 upload

cURL can request Microlink and save the JSON response, but it does not by itself turn the returned asset URL into an S3 object. The following shell sequence uses jq to extract the URL, cURL to fetch the bytes, and the AWS CLI to upload them. It assumes the AWS CLI is configured for the intended account and bucket, and that jq is installed.

set -euo pipefail

TARGET_URL="https://example.com"
BUCKET="your-bucket-name"
KEY="captures/example.com/homepage.jpg"

curl --fail --silent --show-error --get "https://api.microlink.io" \
  --data-urlencode "url=${TARGET_URL}" \
  --data-urlencode "screenshot=true" \
  -o microlink-response.json

SCREENSHOT_URL="$(jq -er '.data.screenshot.url' microlink-response.json)"
curl --fail --location --silent --show-error "$SCREENSHOT_URL" -o screenshot.jpg
aws s3 cp screenshot.jpg "s3://${BUCKET}/${KEY}" --content-type image/jpeg

Set the upload content type to the actual format you requested or received. If you use a different screenshot format, update both the local file extension and --content-type. For authenticated Microlink requests, add the documented authentication using a protected environment variable or another secret mechanism.

6. Choose keys, content type, and access behavior

Object key strategy

Use a deterministic key when a new capture should replace the prior object at the same location, such as a periodically refreshed preview. Use a unique key containing a capture identifier or timestamp when each capture must remain available. Ensure the key cannot be manipulated into an unintended path by untrusted input.

Content type and extension

Use the asset response’s HTTP Content-Type header when available, or the image type in Microlink’s screenshot metadata. Match the S3 object metadata and filename extension to the real format. A wrong content type can cause browsers and downstream tools to handle the object incorrectly.

Visibility and retention

Do not assume a newly uploaded object should be public. Keep it private unless public delivery is a deliberate requirement. Choose an application or CDN access path that fits your use case, and set retention or lifecycle behavior in AWS according to your storage requirements. The reviewed sources do not prescribe a universal access policy or lifecycle configuration.

7. Handle failures and retries

There are three independent network boundaries: Microlink’s capture request, retrieval of its screenshot asset, and the S3 upload. Report failures with the boundary and status so operators can tell which step needs attention.

  • Capture request fails: check the target URL, request parameters, authentication if used, and the returned HTTP status and response body.
  • Capture succeeds but has no screenshot URL: inspect the full response and confirm screenshot is enabled and the response path matches the current API behavior.
  • Asset fetch fails: check the asset URL, its availability, redirects, and HTTP status. Do not treat a successful capture response as proof that the subsequent asset fetch succeeded.
  • S3 upload fails: check the bucket, region, credentials, permissions, and the AWS service error. AWS documents PutObject errors, including oversized object conditions; surface the error and decide whether it is retryable.

Retry transient network and service failures with a bounded retry policy and backoff. Avoid blind retries for permanent errors such as invalid input or missing permissions. If a retry repeats the upload, a deterministic key makes the operation naturally replace the same object; use a unique key only when repeated attempts should create separate retained captures. Track whether the upload completed before retrying the full capture so a retry does not create unwanted duplicate captures.

8. Performance, reliability, and cost considerations

  • Latency: the workflow waits for capture, then asset retrieval, then upload. Keep the transfer server-side and avoid unnecessary copies of the image bytes.
  • Memory: examples above buffer the full screenshot in memory. For large captures or high concurrency, account for the number and size of simultaneous buffers and use a streaming approach where supported by the HTTP and S3 clients.
  • Timeouts: choose timeouts that fit the capture and transfer behavior of your workload. Treat the capture request timeout and asset download timeout as separate boundaries.
  • Reliability: log the target, S3 key, stage, status, and request correlation details that your services provide. Avoid logging credentials or sensitive page content.
  • Storage and capture cost: total cost depends on your Microlink plan and usage plus S3 storage and requests. The supplied references do not establish a workload-specific cost estimate; check current vendor pricing and your AWS billing configuration.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a screenshot or PDF, and you can then upload the returned bytes to S3 with the same server-side object-storage step. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());

ScreenshotNeo accepts cookie banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. After receiving the image bytes, upload them to S3 using the PutObject pattern above. Sign up for 1,000 free screenshots a month, with no card.

Troubleshooting

Symptom Likely cause What to check or change
Microlink request returns an error Invalid target or parameter, unavailable response, or authentication issue Inspect the HTTP status and response body; validate the target and current API request format.
Screenshot URL is undefined Screenshot was not enabled, the request failed in a way not caught by the code, or the response shape differs Check the full JSON response and current screenshot parameter documentation before changing the extraction path.
Asset fetch returns a non-success status The returned asset could not be retrieved, or the URL redirects or expires Fetch from the server, follow redirects where appropriate, check the status, and handle asset retrieval as its own failure stage.
Object appears corrupted or downloads with the wrong type Bytes were transformed, truncated, or labeled with the wrong content type or extension Upload the raw response bytes and set metadata from the actual asset response.
S3 reports access denied Credentials do not have permission for the target bucket/key or the process is using the wrong identity Check the active AWS identity, bucket, region, and required permissions.
S3 reports a missing bucket or wrong endpoint Bucket name or region does not match the intended destination Verify the bucket and configure the client for its region.
Repeated jobs overwrite a capture The key is deterministic Include a capture ID or timestamp in the key if each run must be retained.
Repeated jobs create too many objects The key is unique for every retry or schedule run Use a stable key when only the latest result is needed, and define retention for historical captures.

FAQ

The documented flow returns a screenshot asset URL. The application retrieves the bytes and uploads them to its own bucket.

Can I use this from browser code?

The capture can be initiated by a browser, but privileged S3 credentials belong in a trusted server-side component. A browser upload design needs an appropriately scoped upload mechanism rather than embedded cloud credentials.

Use your S3 copy when your application needs the object in its own storage and access-control lifecycle. Do not assume the Microlink asset URL is a permanent URL under your control.

How do I keep multiple versions?

Give each capture a distinct object key, such as one containing a capture ID or timestamp, and define how long those objects should be retained.

Sources