ScreenshotNeo

BlogHow-to

How to generate website screenshots with ApiFlash in WordPress

Call ApiFlash securely from WordPress with PHP, handle image and JSON responses, tune capture settings, and troubleshoot common errors.

By the ScreenshotNeo team4 October 202611 min read

To generate website screenshots with ApiFlash in WordPress, make the API request from server-side PHP with WordPress’s HTTP API. Keep the ApiFlash access key on the server, validate the target URL, check both transport and HTTP errors, then return or store the image bytes. ApiFlash’s endpoint is https://api.apiflash.com/v1/urltoimage; it accepts GET query parameters or POST form data and requires a complete target URL with http:// or https://. See the ApiFlash documentation and WordPress references for wp_remote_get() and wp_safe_remote_get().

The example below is a site-specific plugin implementation, not an official ApiFlash WordPress plugin. It adds an authenticated shortcode, [apiflash_screenshot url="https://example.com"], which requests the screenshot on the server and displays it. Put the plugin in wp-content/plugins/apiflash-screenshot/apiflash-screenshot.php, configure the key in wp-config.php, then activate it in WordPress.

1. Store your ApiFlash key on the server

Add the key to wp-config.php, above the “stop editing” line. Do not place it in a theme template, JavaScript bundle, shortcode output, or a public image URL.

define( 'APIFLASH_ACCESS_KEY', 'replace-with-your-api-key' );

Restrict access to the server configuration file according to your hosting setup. ApiFlash’s documentation also describes proxying requests to keep the key from end users.

2. Add the shortcode plugin

This example limits screenshot generation to logged-in users who can edit posts. It renders the image as a data URI, so the credential-bearing ApiFlash request stays server-side. Data URIs are convenient for a small number of images; for reusable or large images, use the storage approach in the next section.

<?php
/**
 * Plugin Name: ApiFlash Screenshot Shortcode
 * Description: Server-side ApiFlash screenshots for authorized editors.
 * Version: 1.0.0
 */

add_shortcode( 'apiflash_screenshot', 'site_apiflash_screenshot_shortcode' );

function site_apiflash_screenshot_shortcode( $atts ) {
    if ( ! defined( 'APIFLASH_ACCESS_KEY' ) || ! APIFLASH_ACCESS_KEY ) {
        return current_user_can( 'edit_posts' )
            ? '<p>Configure APIFLASH_ACCESS_KEY in wp-config.php.</p>'
            : '';
    }

    if ( ! current_user_can( 'edit_posts' ) ) {
        return '';
    }

    $atts = shortcode_atts(
        array( 'url' => '' ),
        $atts,
        'apiflash_screenshot'
    );

    $url = esc_url_raw( trim( $atts['url'] ), array( 'http', 'https' ) );
    if ( ! $url || ! wp_http_validate_url( $url ) ) {
        return '<p>Provide a valid HTTP or HTTPS URL.</p>';
    }

    $api_url = add_query_arg(
        array(
            'access_key' => APIFLASH_ACCESS_KEY,
            'url'        => $url,
            'width'      => 1280,
            'height'     => 800,
            'format'     => 'png',
            'response_type' => 'image',
        ),
        'https://api.apiflash.com/v1/urltoimage'
    );

    $response = wp_safe_remote_get(
        $api_url,
        array(
            'timeout'             => 60,
            'redirection'         => 3,
            'limit_response_size' => 12 * MB_IN_BYTES,
            'headers'             => array( 'Accept' => 'image/png' ),
        )
    );

    if ( is_wp_error( $response ) ) {
        return current_user_can( 'edit_posts' )
            ? '<p>Screenshot request failed: ' . esc_html( $response->get_error_message() ) . '</p>'
            : '';
    }

    $status = wp_remote_retrieve_response_code( $response );
    $body   = wp_remote_retrieve_body( $response );
    $type   = wp_remote_retrieve_header( $response, 'content-type' );

    if ( 200 !== $status || '' === $body || false === strpos( (string) $type, 'image/' ) ) {
        return current_user_can( 'edit_posts' )
            ? '<p>ApiFlash returned HTTP ' . esc_html( (string) $status ) . '. Check the key, quota, request parameters, and target page.</p>'
            : '';
    }

    return sprintf(
        '<img alt="Website screenshot" loading="lazy" style="max-width:100%%;height:auto" src="data:%1$s;base64,%2$s">',
        esc_attr( $type ),
        esc_attr( base64_encode( $body ) )
    );
}

This deliberately uses wp_safe_remote_get() and validates the target before making a request. If your feature must capture arbitrary public URLs, review the URL validation and outbound-request policy for your hosting environment to reduce server-side request forgery (SSRF) risk. Do not expose an unrestricted public shortcode or AJAX action that lets anonymous visitors make your server fetch arbitrary destinations.

3. Choose image bytes or JSON output

By default, ApiFlash returns screenshot image data. The shortcode above requests image output and places those bytes in the page. If WordPress instead needs a screenshot URL or extracted page data, request response_type=json, decode the response, and validate the JSON before using its fields. HTML or text extraction can be requested with the documented options.

Mode Use it when WordPress handling
Image response The current request needs the screenshot bytes Check HTTP status and content type; save the body as an image or encode it for output.
JSON response You need the screenshot link, metadata, or requested extracted content Decode JSON and check for a valid response before reading fields. Do not assume the body is an image.

Do not put an access-key-bearing ApiFlash URL in a visitor-visible img src. For a persistent image, save validated image bytes to a controlled location in the WordPress uploads directory and render that local URL. Use a deterministic cache key based on the target and capture settings, and refresh it according to your content workflow; avoid creating a new file on every page view.

4. Set capture options for the page

ApiFlash’s parameters let you control the rendering and output. The exact set available to your account may depend on plan capabilities; ApiFlash documents HTTP 403 for an unsupported feature.

Parameter Behavior and when to use it
url Required complete target URL, including the protocol. Encode it as a query value or send it as POST form data.
width, height Viewport dimensions. Defaults are 1920 by 1080. Pick dimensions that match the intended display or device layout.
full_page=true Capture the full page height; height is ignored in this mode.
format JPEG (the default), PNG, or WebP. Pick a format supported by your downstream use.
quality Controls JPEG and WebP quality; it does not apply to PNG.
wait_until Choose network_idle (the documented default), dom_loaded, or page_loaded to match how the page finishes rendering.
wait_for Wait for a CSS selector to appear, useful for content rendered after initial page load or lazy-loaded elements.
delay Add a fixed wait in milliseconds. Prefer a page-state or selector wait when possible because a fixed delay may be too short or waste time.
element Capture the first element matching a CSS selector rather than the whole viewport.
fresh=true, ttl Use fresh=true to bypass cache reuse for that call; set ttl to control screenshot cache duration.
response_type=json Return JSON containing a screenshot link; requested HTML or text extraction can also be included.

For example, change the query array in the plugin to request a full-page WebP after a selector appears:

array(
    'access_key' => APIFLASH_ACCESS_KEY,
    'url' => $url,
    'full_page' => 'true',
    'format' => 'webp',
    'quality' => 80,
    'wait_for' => '.article-content',
    'ttl' => 3600,
)

Use only options you need. The source page may behave differently when a cookie banner, authentication wall, bot check, or geographic rule is present. ApiFlash captures with Chrome on Linux, so system fonts can differ from Windows or macOS. Serving web fonts instead of relying on operating-system fonts helps make text rendering more consistent.

5. Test the API outside WordPress

Before debugging a plugin, verify the key and target with a direct request. ApiFlash accepts GET query parameters and POST form data. The GET form is convenient for a small capture; avoid sharing a URL containing the access key in logs, screenshots, or public issue reports.

cURL

curl --get 'https://api.apiflash.com/v1/urltoimage' \
  --data-urlencode 'access_key=YOUR_API_KEY' \
  --data-urlencode 'url=https://example.com' \
  --data-urlencode 'width=1280' \
  --data-urlencode 'height=800' \
  --data-urlencode 'format=png' \
  --output screenshot.png

Python

import requests

response = requests.get(
    "https://api.apiflash.com/v1/urltoimage",
    params={
        "access_key": "YOUR_API_KEY",
        "url": "https://example.com",
        "width": 1280,
        "height": 800,
        "format": "png",
    },
    timeout=60,
)
response.raise_for_status()
content_type = response.headers.get("content-type", "")
if not content_type.startswith("image/"):
    raise RuntimeError(f"Expected image response, got {content_type}: {response.text[:500]}")
with open("screenshot.png", "wb") as image_file:
    image_file.write(response.content)

Node.js

const params = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
  width: '1280',
  height: '800',
  format: 'png',
});

const response = await fetch(
  `https://api.apiflash.com/v1/urltoimage?${params}`,
  { signal: AbortSignal.timeout(60_000) }
);
if (!response.ok) {
  throw new Error(`ApiFlash returned HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get('content-type') || '';
if (!contentType.startsWith('image/')) {
  throw new Error(`Expected image response, got ${contentType}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('screenshot.png', image));

6. Use POST when appropriate

For long option sets or URLs that make a GET request unwieldy, ApiFlash also accepts POST form data. In WordPress, wp_remote_post() sends the form body. Keep the API key server-side and handle the response exactly as for GET.

$response = wp_remote_post(
    'https://api.apiflash.com/v1/urltoimage',
    array(
        'timeout' => 60,
        'body' => array(
            'access_key' => APIFLASH_ACCESS_KEY,
            'url' => $url,
            'width' => 1280,
            'height' => 800,
            'format' => 'png',
        ),
    )
);

7. Handle errors, quota, and request limits

There are two failure layers: WordPress may fail to connect or time out before receiving an HTTP response, or ApiFlash may return an HTTP error. Check both. ApiFlash documents these status codes:

Status Likely cause What to do
400 Invalid parameters or target cannot be captured Check the complete URL, parameter names and values, and whether the target is reachable.
401 Invalid or revoked access key Check the server-side key and rotate it if necessary.
402 Monthly screenshot quota exceeded Review usage and plan capacity; avoid unnecessary unique captures.
403 Requested feature is unsupported by the plan Remove the unsupported option or check plan terms.
429 Rate limit exceeded Queue and spread requests; honor retry timing and avoid bursts beyond the documented allowance.
500 Capture failure that the API could not handle Retry cautiously, inspect the target and options, and preserve an error log without the key.

The successful response includes X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset headers. Log these values for monitoring. The documented rate is 20 requests per second with a burst size of 400; excess requests can be delayed or receive 429. ApiFlash also limits repeated failed requests with identical parameters to five per hour. Its FAQ says cached screenshots and failed screenshots do not count toward monthly quota.

For production, define a retry policy only for transient connection errors, 429, and selected 5xx responses. Use exponential backoff with jitter and a retry limit; do not retry invalid input, authentication failures, unsupported features, or exhausted quota. On a timeout, the remote capture may have completed even if WordPress did not receive the result, so use caching or an idempotent storage key to avoid creating duplicate work.

8. Performance, reliability, and cost

  • Cache at the WordPress layer. Reuse a saved screenshot until its content should be refreshed. ApiFlash also supports screenshot caching and a caller-controlled ttl; fresh=true bypasses reuse for a call.
  • Keep capture work out of high-traffic page renders. A shortcode that calls the API synchronously can slow the visitor’s request. Generate on an editor action, scheduled task, or background queue, then serve the stored image.
  • Size the output for its use. Smaller viewport or output dimensions and a compressed format can reduce transferred data. Choose quality only for JPEG or WebP.
  • Choose waits deliberately. The default network_idle may be unsuitable for pages with continuous network activity. Use dom_loaded, page_loaded, a specific wait_for selector, or a delay when the page needs it.
  • Budget for successful unique captures. The pricing page accessed October 3, 2026 listed Free at 100 screenshots/month, Lite at 1,000 for $7/month, Medium at 10,000 for $35/month, and Large at 100,000 for $180/month, with custom Enterprise terms. Check the current ApiFlash pricing page before choosing a plan. ApiFlash says cached and failed screenshots do not count against quota.

9. Troubleshooting

The shortcode displays no image

Confirm the user has the required capability, the shortcode URL is valid, the plugin is active, and the key is defined. The sample intentionally returns an empty result to unauthorized visitors.

WordPress reports a cURL or transport error

The server may not be able to make outbound HTTPS requests, or the request exceeded the configured timeout. Check hosting firewall and TLS configuration, then test the endpoint from the server environment. Increase the timeout only when the capture genuinely needs more time.

The response is not an image

An API error response may be text or JSON. Inspect the HTTP status and response body in a protected server log; do not print the access key or expose detailed upstream errors to public visitors. If you requested response_type=json, decode JSON instead of treating the body as image bytes.

The page is cut off or dynamic content is missing

Use full_page=true for full height, or wait for the element containing the content with wait_for. If the target keeps network connections open, try a different documented wait_until state. Check lazy-loaded content and target-page access restrictions.

The screenshot looks different from my computer

ApiFlash uses Chrome on Linux. System fonts may differ; serve the intended web fonts. Also specify viewport dimensions because responsive breakpoints can change layout.

Requests return 401, 402, 403, or 429

These indicate a key problem, exhausted quota, unsupported feature, or rate limiting, respectively. Check the key and quota, review plan-supported parameters, and queue calls to stay within the published request rate.

10. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. Its clean-shot flow accepts cookie and consent banners like a visitor, then removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status.

cURL:

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

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for options including full-page capture, CSS element capture, device presets, custom waits, PDF settings, caching, and asynchronous jobs. Its MCP server gives AI agents tools named take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up free and get 1,000 screenshots a month with no card.

11. FAQ

Does ApiFlash have an official WordPress plugin?

The reviewed sources document the API and WordPress HTTP functions, but do not establish an official ApiFlash plugin. The code here is a custom implementation example.

Can I use a screenshot in a public post?

Yes. Prefer storing the image in WordPress uploads and displaying that local file. Avoid exposing the API key in a public source URL, and ensure you have permission to capture and republish the target page.

Should I use a fixed delay or wait for a selector?

Use a selector or page-state wait when the needed content has a clear readiness condition. A delay is useful only when no reliable condition is available.

Do cached or failed ApiFlash captures use monthly quota?

ApiFlash’s FAQ says cached screenshots and failed screenshots do not count toward the monthly screenshot quota.

Which format should I save?

Use PNG when lossless rendering is important, JPEG for broadly compatible photographic output, or WebP when supported by the consuming site. ApiFlash defaults to JPEG; quality applies to JPEG and WebP.