Screenshot API for WordPress: Quick Start and Examples
Learn how WordPress REST API discovery differs from webpage screenshots, then capture pages with server-side code, plugins, or ScreenshotNeo.
To capture a WordPress page as an image, send its public URL to a screenshot rendering service from server-side code. WordPress itself provides a REST API for exchanging site data as JSON; it does not include a universal screenshot endpoint. The WordPress REST API and a screenshot API are separate systems.
This guide shows how to discover a WordPress REST API, capture pages with a hosted provider, build a small WordPress endpoint, handle protected pages safely, and troubleshoot common failures. It also includes cURL, Python, Node.js and PHP examples.
What the WordPress REST API does
The WordPress REST API lets applications interact with a site by sending and receiving JSON. Every WordPress installation has its own API root, normally /wp-json/.
curl https://your-site.example/wp-json/
The response is an index of namespaces and routes. You can inspect a route with an HTTP OPTIONS request when the endpoint supports it:
curl -i -X OPTIONS https://your-site.example/wp-json/wp/v2/posts
Use this API to read posts, pages, media and custom data. Use a screenshot renderer to turn the rendered webpage into PNG, JPEG, WebP or PDF. A request to /wp-json/wp/v2/posts returns data; a screenshot request loads the page in a browser and captures pixels.
Choose an integration
| Approach | Best for | Trade-offs |
|---|---|---|
| Hosted screenshot API | Automated previews, reports and monitoring | Requires provider credentials and request handling |
| WordPress plugin or shortcode | Editors who need screenshots inside content | Depends on plugin maintenance and provider compatibility |
| Your own browser worker | Full control over Chromium and network policy | You operate browsers, queues, scaling and failures |
The Urlbox WordPress Screenshots repository documents a shortcode-based plugin example. Review a plugin’s current maintenance, supported WordPress versions and provider documentation before installing it.
Quick start with a hosted screenshot API
- Choose a provider and read its current endpoint and authentication documentation.
- Keep the API key in a server environment variable.
- Send the complete public page URL and capture options from server-side code.
- Check the HTTP status and content type before saving the response.
- Store the image outside temporary directories if it will be reused.
Provider syntax is not universal. For example, one service may accept a GET request while another requires a bearer-authenticated POST. Treat each provider’s endpoint, options and response format as provider-specific.
cURL example
curl -X POST 'https://provider.example/v1/screenshot' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://your-site.example/sample-page/",
"format": "png",
"full_page": true
}' \
--output page.png
Replace the endpoint and option names with those in your provider’s documentation. Some APIs return image bytes directly; others return JSON containing a download URL.
Python example
import os
import requests
api_key = os.environ['SCREENSHOT_API_KEY']
target = 'https://your-site.example/sample-page/'
response = requests.post(
'https://provider.example/v1/screenshot',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
},
json={
'url': target,
'format': 'png',
'full_page': True,
},
timeout=90,
)
response.raise_for_status()
content_type = response.headers.get('content-type', '')
if 'image/' not in content_type:
raise RuntimeError(f'Expected an image, got {content_type}')
with open('page.png', 'wb') as output:
output.write(response.content)
Node.js example
const apiKey = process.env.SCREENSHOT_API_KEY;
const target = 'https://your-site.example/sample-page/';
const res = await fetch('https://provider.example/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: target,
format: 'png',
full_page: true
})
});
if (!res.ok) {
throw new Error(`Screenshot failed: ${res.status} ${await res.text()}`);
}
const type = res.headers.get('content-type') || '';
if (!type.startsWith('image/')) throw new Error(`Unexpected content type: ${type}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('page.png', buffer));
Calling a screenshot API from WordPress PHP
Keep credentials on the server and call the provider with WordPress’s HTTP API. This example creates an authenticated custom route that returns the captured image.
<?php
add_action('rest_api_init', function () {
register_rest_route('site-tools/v1', '/screenshot', [
'methods' => 'GET',
'callback' => 'site_tools_screenshot',
'permission_callback' => function () {
return current_user_can('manage_options');
},
'args' => [
'url' => [
'required' => true,
'sanitize_callback' => 'esc_url_raw',
],
],
]);
});
function site_tools_screenshot(WP_REST_Request $request) {
$url = $request->get_param('url');
$key = getenv('SCREENSHOT_API_KEY');
if (!$key || !wp_http_validate_url($url)) {
return new WP_Error('invalid_request', 'Missing key or invalid URL', ['status' => 400]);
}
$response = wp_remote_post('https://provider.example/v1/screenshot', [
'timeout' => 90,
'headers' => [
'Authorization' => 'Bearer ' . $key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode([
'url' => $url,
'format' => 'png',
'full_page' => true,
]),
]);
if (is_wp_error($response)) {
return new WP_Error('provider_error', $response->get_error_message(), ['status' => 502]);
}
$status = wp_remote_retrieve_response_code($response);
$type = wp_remote_retrieve_header($response, 'content-type');
$body = wp_remote_retrieve_body($response);
if ($status < 200 || $status >= 300 || strpos($type, 'image/') !== 0) {
return new WP_Error('capture_failed', 'Provider did not return an image', ['status' => 502]);
}
return new WP_REST_Response($body, 200, ['Content-Type' => $type]);
}
Install this in a site-specific plugin rather than a theme if the route should survive a theme change. Add your own allowlist, authentication and rate limit before exposing a route to untrusted users.
Authentication and protected WordPress pages
Public WordPress data is generally available without authentication. Private posts, draft previews and membership pages require authentication or explicit exposure. A remote screenshot service cannot see a page that your site does not make available to it.
- For public pages, send the canonical HTTPS URL.
- For private pages, use a provider feature for custom cookies, headers or authorization if documented.
- Never place a screenshot API key in browser JavaScript, page HTML or a public shortcode attribute.
- Use short-lived preview URLs or a server-side proxy instead of weakening WordPress permissions.
- Check robots, firewall and bot protection rules before allowing the provider’s browser to load the page.
Useful capture options
Exact parameter names vary by provider. Common capabilities include:
| Need | What to configure |
|---|---|
| Long article | Full-page capture, with a suitable height or lazy-image loading |
| Responsive preview | Viewport width and height, or a device preset |
| Retina output | Device scale factor or retina scale |
| Stable layout | Wait for a selector, fixed delay or network idle |
| Dynamic content | Custom JavaScript or a click action before capture |
| Privacy | Block trackers, ads, requests or resource types |
| Branding | Custom CSS, hidden selectors or a selected element |
| Archives | Cache with a chosen TTL and deterministic URL parameters |
Performance, reliability and cost
- Use a normal page URL and avoid redirect chains where possible.
- Wait only for the condition your page needs. A long fixed delay increases latency on every request.
- Capture a single element when a full-page image is unnecessary.
- Cache images whose source content has not changed. Include the relevant page version in your cache key.
- Set a client timeout longer than the provider’s normal render time and handle retries with exponential backoff.
- Do not retry invalid URLs, authentication failures or deterministic 4xx responses.
- For many pages, use a provider’s batch or asynchronous jobs if available instead of opening hundreds of simultaneous connections.
- Measure image bytes, render duration, response status and provider-specific billing headers.
Pricing, quotas, browser versions and retention policies differ by provider. Verify those details in current documentation before committing to an integration.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or 403 from provider | Missing, expired or incorrectly formatted credential | Check the server environment variable and the provider’s required authentication header. |
| WordPress returns JSON instead of an image | You called /wp-json/, which is a data API |
Call the screenshot provider endpoint or your custom proxy route. |
| Blank image | JavaScript has not finished, content is blocked, or the page requires login | Use a selector/network-idle wait, permit required resources, or provide documented cookies or headers. |
| Images missing below the fold | Lazy loading was not triggered | Enable full-page scrolling or provider lazy-image support. |
| Cookie banner covers content | The renderer captured the first viewport before consent handling | Use a consent action or a renderer that can dismiss banners before capture. |
| Request times out | Slow origin, third-party scripts or an oversized page | Block unnecessary resources, wait for a specific selector and increase timeout within provider limits. |
| Private page is a login screen | The remote browser has no authenticated session | Use a short-lived public preview or provider-supported cookies and authorization headers. |
| WordPress route is publicly exploitable | No permission callback or URL allowlist | Require a capability, validate destinations and add rate limiting. |
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP or PDF. It accepts consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets before capture; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the page verdict and billing status.
Use the same server-side pattern with ScreenshotNeo (see the API documentation):
curl -G 'https://api.screenshotneo.com/v1/shot' \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://your-site.example/sample-page/ \
-o shot.webp
import requests
r = requests.get(
'https://api.screenshotneo.com/v1/shot',
params={'access_key': 'YOUR_API_KEY', 'url': 'https://your-site.example/sample-page/'},
timeout=90,
)
r.raise_for_status()
open('shot.webp', 'wb').write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://your-site.example/sample-page/'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
ScreenshotNeo also offers full-page capture with lazy images loaded, CSS-element capture, dark mode, device presets, custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, blocking rules, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.
There is a free plan with 1,000 screenshots per month and no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.
FAQ
Is the WordPress REST API a screenshot API?
No. It exposes WordPress data and routes as JSON. Rendering a page into an image requires a browser-based service, plugin integration or your own browser worker.
Can I capture a draft post?
Only if the renderer can access an authenticated or explicitly shared preview URL. A normal public request cannot read a private draft.
Should the browser call the screenshot provider directly?
No. Keep credentials in server-side code and return only the image or a controlled result to the browser.
What should I store for repeatable screenshots?
Store the target URL, viewport, format, relevant wait settings, capture timestamp and content version. These make cache keys and later comparisons reproducible.
When is a plugin the right choice?
Use one when editors need screenshots inside WordPress and the plugin is maintained for your WordPress and provider versions. Use server-side code when you need custom security, queues or batch processing.


