ScreenshotNeo

BlogHow-to

How to Capture a Website Screenshot with ApiFlash in PHP

Capture a website screenshot with ApiFlash from PHP, save the image safely, and handle formats, loading, errors, quotas, and security.

By the ScreenshotNeo team4 October 202610 min read

Use PHP to send an HTTP request to ApiFlash’s urltoimage endpoint, check that the response is successful, then save its image bytes. The target URL must include https:// or http://, and every request needs a valid ApiFlash access key. For production code, check the HTTP status and response type before writing the body to an image file; otherwise an API error response could be saved with an image extension.

1. Get an ApiFlash access key

  1. Create or sign in to an ApiFlash account and retrieve the access key from its dashboard.
  2. Keep the key in a server-side environment variable or secret store. Do not put it in browser JavaScript or a public page.
  3. Choose the target URL, including its scheme, for example https://example.com.

ApiFlash accepts GET requests with query parameters or POST requests with form data. The examples below use GET. http_build_query() safely encodes the query values, including the target URL and any option values. See the ApiFlash Screenshot API Documentation for the full parameter reference.

2. Minimal PHP example

This follows ApiFlash’s documented minimal PHP pattern. It saves the response body directly, so use the production version in the next section when you need to distinguish an image from an error response.

<?php
$params = http_build_query([
    'access_key' => 'YOUR_ACCESS_KEY',
    'url' => 'https://example.com',
]);

$imageData = file_get_contents(
    'https://api.apiflash.com/v1/urltoimage?' . $params
);

if ($imageData === false) {
    throw new RuntimeException('ApiFlash request failed');
}

file_put_contents('screenshot.jpeg', $imageData);

Replace the placeholder key and target URL. The response defaults to JPEG image data; the example writes it to screenshot.jpeg.

3. Production PHP with status and content checks

PHP’s file_get_contents() can read an HTTP response body, but its return value alone does not tell your application that the body is a valid screenshot. The following cURL example inspects the status and content type before saving. It requires the PHP cURL extension.

<?php
declare(strict_types=1);

$accessKey = getenv('APIFLASH_ACCESS_KEY');
if (!$accessKey) {
    throw new RuntimeException('Set APIFLASH_ACCESS_KEY in the server environment');
}

$targetUrl = 'https://example.com';
$params = [
    'access_key' => $accessKey,
    'url' => $targetUrl,
];
$requestUrl = 'https://api.apiflash.com/v1/urltoimage?' . http_build_query($params);

$ch = curl_init($requestUrl);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 90,
    CURLOPT_HEADER => false,
]);

$body = curl_exec($ch);
if ($body === false) {
    $message = curl_error($ch);
    curl_close($ch);
    throw new RuntimeException('Transport error calling ApiFlash: ' . $message);
}
$status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$contentType = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);

if ($status < 200 || $status >= 300) {
    throw new RuntimeException('ApiFlash returned HTTP ' . $status . ': ' . substr($body, 0, 1000));
}
if (!str_starts_with(strtolower($contentType), 'image/')) {
    throw new RuntimeException('Expected image data, received Content-Type: ' . $contentType);
}

$outputPath = __DIR__ . '/screenshot.jpeg';
if (file_put_contents($outputPath, $body) === false) {
    throw new RuntimeException('Could not write screenshot to ' . $outputPath);
}
echo 'Saved screenshot to ' . $outputPath . PHP_EOL;

Set APIFLASH_ACCESS_KEY in the PHP process environment before running this script. The 90-second timeout is an application-side ceiling, not an ApiFlash performance guarantee. Choose a timeout that fits your request budget. In a web application, return a controlled error to the caller and log useful status details server-side; avoid logging the full request URL because it includes the access key.

4. cURL, Python, and Node.js equivalents

These examples demonstrate the same basic request from common backend environments. Store the key as a server-side secret. Each writes the returned bytes only after checking for an HTTP success response.

cURL

curl --fail-with-body -G 'https://api.apiflash.com/v1/urltoimage' \
  --data-urlencode 'access_key=YOUR_ACCESS_KEY' \
  --data-urlencode 'url=https://example.com' \
  -o screenshot.jpeg

--fail-with-body exits with an error status for HTTP errors while retaining the response body for diagnosis. Do not publish a command containing a real key in shell history or shared logs.

Python

import os
import requests

key = os.environ['APIFLASH_ACCESS_KEY']
response = requests.get(
    'https://api.apiflash.com/v1/urltoimage',
    params={'access_key': key, 'url': 'https://example.com'},
    timeout=90,
)
response.raise_for_status()
if not response.headers.get('Content-Type', '').lower().startswith('image/'):
    raise RuntimeError(f"Expected image data, got {response.headers.get('Content-Type')}")
with open('screenshot.jpeg', 'wb') as output:
    output.write(response.content)

Node.js

const key = process.env.APIFLASH_ACCESS_KEY;
if (!key) throw new Error('Set APIFLASH_ACCESS_KEY');

const query = new URLSearchParams({
  access_key: key,
  url: 'https://example.com',
});
const response = await fetch(
  `https://api.apiflash.com/v1/urltoimage?${query}`,
  { signal: AbortSignal.timeout(90_000) },
);
if (!response.ok) {
  throw new Error(`ApiFlash returned HTTP ${response.status}: ${await response.text()}`);
}
const contentType = response.headers.get('content-type') || '';
if (!contentType.toLowerCase().startsWith('image/')) {
  throw new Error(`Expected image data, got Content-Type: ${contentType}`);
}
const bytes = Buffer.from(await response.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) => writeFile('screenshot.jpeg', bytes));

5. Choose capture parameters

ApiFlash’s endpoint is https://api.apiflash.com/v1/urltoimage. The required parameters are a valid access_key and a complete target url. Add optional parameters to the same PHP array before calling http_build_query().

Need Parameter or behavior Practical note
Choose output format format: JPEG (default), PNG, or WebP Use a filename extension consistent with the chosen format. The default response is image data.
Adjust compression quality Applies to JPEG and WebP; it does not tune PNG compression in the same way.
Set viewport width, height The documented default viewport is 1920 × 1080. Respect the service’s pixel and total-area limits; WebP also has width and height limits after scale_factor.
Capture the entire page full_page=true Captures the full page height. In full-page mode, height and thumbnail_width are ignored, as is element capture.
Wait for dynamic content wait_for, wait_until, or delay Prefer a meaningful selector or loading milestone over a fixed delay. A missing wait_for selector aborts after 15 seconds; delay accepts up to 10 seconds.
Trigger lazy content scroll_page=true Scrolling can trigger lazy-loaded elements and animations before the capture.
Bypass cached capture fresh=true The documented default cache TTL is 86,400 seconds; the accepted TTL range is 0–2,592,000 seconds.
Return JSON metadata response_type=json Returns JSON with a screenshot URL. Extraction URLs can be included when extraction options are enabled.
Extract page content extract_html, extract_text These switches require JSON response mode; handle the JSON response instead of saving it as an image.

Check the current API documentation for parameter spelling, supported bounds, and plan availability. A response mode change matters to your code: if you request JSON, parse the JSON and use its screenshot URL instead of writing the response body to a JPEG file.

Adding options in PHP

$params = [
    'access_key' => getenv('APIFLASH_ACCESS_KEY'),
    'url' => 'https://example.com',
    'format' => 'png',
    'full_page' => 'true',
    'wait_until' => 'networkidle',
];
$query = http_build_query($params);

Use values and parameter names accepted by ApiFlash’s current documentation. Avoid setting contradictory loading options without checking their documented precedence.

6. Wait for the page you actually need

The FAQ says ApiFlash waits for network idle by default. A page that keeps polling, loads content after user interaction, or hydrates late may need a more specific condition.

  1. Use wait_for when a particular element indicates that the useful content is ready.
  2. Use wait_until to select a documented browser loading milestone appropriate to the page.
  3. Use delay only when a short fixed pause is the practical choice. Its documented maximum is 10 seconds.
  4. Consider scroll_page=true if images or sections load only as the page is scrolled.

Longer waits do not guarantee success: the target can still time out, reject automated access, or return a blank page. A selector wait is also a failure condition if the selector never appears; ApiFlash documents a 15-second abort for that case.

7. Return JSON or extract content

Use the default image response when the caller needs image bytes. Set response_type=json if your application needs a JSON result containing a screenshot URL, or wants the extraction URLs available through extract_html or extract_text. The extraction switches require JSON mode.

Do not reuse image-saving code for JSON mode: first check the response content type and status, parse JSON, then decide whether to download the returned screenshot URL. Treat extracted page content as untrusted input, just as you would content fetched directly from a website.

8. Common errors and fixes

Symptom or status Likely cause What to do
HTTP 400 Invalid parameter or target that cannot be captured Check parameter names and allowed values, URL-encode through http_build_query(), and confirm the target URL is complete and reachable.
HTTP 401 Access key is invalid or revoked Verify the server secret and rotate it through the provider’s dashboard if needed. Do not expose it in client code.
HTTP 402 Monthly quota exceeded Check account quota and your usage; avoid retry loops that spend more quota.
HTTP 403 Your plan does not support requested parameters Check plan availability for each option and remove or replace unsupported parameters.
HTTP 429 Rate limit reached Back off with jitter, reduce request bursts, and retry only when appropriate.
HTTP 500 API-side capture failure Record a request identifier if provided, retry a limited number of times with backoff, and inspect whether the target consistently causes failure.
PHP warning or false from file_get_contents() Network, TLS, PHP configuration, or remote failure Check server connectivity and PHP stream settings; cURL provides clearer transport diagnostics and status handling.
Saved “image” is JSON or HTML Error body or JSON response was saved as image data Check HTTP status and Content-Type; parse JSON mode separately.
Blank or incomplete capture Content rendered late, target blocks automation, or lazy content was not triggered Wait for a content selector or suitable milestone, try scrolling, and inspect the page’s behavior and access restrictions.
Wrong dimensions or format Full-page mode ignores some dimensions; extension and format may not match Review the full-page rules, requested format, scale, and documented image-size limits.

ApiFlash documents that identical requests which fail to capture a screenshot are limited to five per hour. Avoid tight retry loops for a failing target.

9. Security and public endpoint design

  • Keep credentials server-side. A request from frontend JavaScript exposes the key to visitors. ApiFlash’s download guide says frontend calls should be reserved for trusted or internal situations.
  • Constrain user-supplied URLs. If you expose your own screenshot endpoint, allow only intended destinations and rate-limit callers. An unrestricted proxy can be abused to capture arbitrary sites and consume your quota.
  • Protect authenticated captures. ApiFlash’s FAQ describes passing session cookies using the cookies parameter after normal authentication. Treat cookies and custom headers as credentials, and capture only content the requester may access.
  • Limit data in logs. Do not log access keys, cookies, authorization headers, or sensitive screenshot contents.
  • Respect content rights. A screenshot can contain third-party page material. Creating the screenshot does not itself grant rights to republish that content; review the applicable terms and rights for your use.

See ApiFlash’s terms of service for its treatment of user data and screenshot deliverables.

10. Performance, reliability, and cost

Reuse identical captures when freshness is unnecessary: the documented default cache TTL is 86,400 seconds. Set the documented cache controls deliberately, and use fresh=true only when the page must be recaptured. The accepted TTL range is 0 to 2,592,000 seconds.

Bound your PHP connection and total request time, and avoid holding a web worker indefinitely. For larger workloads, queue capture requests and process them with controlled concurrency. ApiFlash’s concurrency guide, last updated January 25, 2023, says there is no limit on concurrent requests while also describing per-second rate limits. Concurrency permission does not remove the per-second limit; consult the guide and current account limits before scaling.

Quota and pricing depend on the account and plan; check the account’s quota endpoint or quota-related response header rather than relying on an illustrative quota example. Handle HTTP 402 explicitly and surface a useful error to operators. Retries should be bounded: a failed capture can be rate-limited after five identical failures per hour.

11. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. It accepts one GET request and returns a PNG, JPEG, WebP, or PDF. Its API uses familiar screenshot parameter names, which can make switching straightforward. See the ScreenshotNeo API documentation.

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

ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers say which verdict and billing status applied. Its MCP server lets AI agents use 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 screenshots, and every feature is on every plan.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

12. FAQ

Can I use POST instead of GET?

Yes. ApiFlash documents GET query parameters and POST form data. Use the method that best fits your request handling and credential exposure controls; neither makes a key safe to expose in browser code.

Does saving a screenshot require a local browser?

No. ApiFlash performs the capture remotely; your PHP code makes an HTTP request and stores the returned image bytes.

Can I capture a page that requires login?

The FAQ describes sending session cookies with the cookies parameter after authentication. Protect those cookies as secrets and ensure the capture is authorized.

Why did a full-page result ignore my height?

ApiFlash documents that height is ignored when full_page=true; the full page height is captured instead.

Where can I check remaining quota?

Use ApiFlash’s documented quota endpoint or inspect the quota-related response header, and treat the account’s actual result as authoritative.

Sources