How to Use ApiFlash in a Django Project to Capture Webpage Screenshots
Capture webpages with ApiFlash from a Django view. Configure the API key securely, return image bytes, tune capture options, and handle common errors.
To capture a webpage with ApiFlash in Django, call its HTTPS endpoint from a Django view, keep the access key on the server, and return the image bytes in an HttpResponse. ApiFlash accepts a fully qualified target URL and capture parameters at https://api.apiflash.com/v1/urltoimage. This example uses the third-party Python requests library; ApiFlash documents the HTTP API rather than a Django-specific package.
1. Install the HTTP client and configure the key
Install requests in your project environment:
python -m pip install requests
Set APIFLASH_ACCESS_KEY in your deployment environment or local environment file, and read it in Django settings. Do not commit the secret or place it in frontend JavaScript, query links, or templates.
# settings.py
import os
APIFLASH_ACCESS_KEY = os.environ["APIFLASH_ACCESS_KEY"]
ApiFlash warns that frontend API calls expose the access key. Calling it from Django keeps the credential on the server.
2. Add a Django view that returns the screenshot
This view accepts a URL, requests PNG output, applies connection and read timeouts, checks the upstream status and content type, and sends the bytes back as an inline image.
# views.py
import requests
from django.conf import settings
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import require_GET
APIFLASH_ENDPOINT = "https://api.apiflash.com/v1/urltoimage"
@require_GET
def screenshot(request):
target = request.GET.get("url", "").strip()
if not target.startswith(("https://", "http://")):
return JsonResponse(
{"error": "Provide a complete http(s) URL."},
status=400,
)
params = {
"access_key": settings.APIFLASH_ACCESS_KEY,
"url": target,
"format": "png",
# Add documented capture options here, for example:
# "full_page": "true",
# "wait_for": ".page-ready",
}
try:
upstream = requests.get(
APIFLASH_ENDPOINT,
params=params,
timeout=(5, 45),
)
except requests.RequestException:
# Log a redacted exception on the server if operationally useful.
return JsonResponse(
{"error": "Screenshot service request failed."},
status=502,
)
if upstream.status_code != 200:
# Record status and a safely redacted diagnostic in server logs.
return JsonResponse(
{"error": "Screenshot capture failed."},
status=502,
)
content_type = upstream.headers.get("Content-Type", "image/jpeg")
if not content_type.startswith("image/"):
return JsonResponse(
{"error": "Screenshot service returned an unexpected response."},
status=502,
)
return HttpResponse(upstream.content, content_type=content_type)
Wire the view into a URL configuration:
# urls.py
from django.urls import path
from .views import screenshot
urlpatterns = [
path("screenshot/", screenshot, name="screenshot"),
]
For a quick local request, URL-encode the target URL in the query string. A browser can then display the returned image directly:
curl --get 'http://127.0.0.1:8000/screenshot/' \
--data-urlencode 'url=https://example.com' \
--output screenshot.png
ApiFlash documents that its default response is image bytes with image response headers. If your Django feature needs a downloadable file instead of an inline image, set a download disposition:
response = HttpResponse(upstream.content, content_type=content_type)
response["Content-Disposition"] = 'attachment; filename="screenshot.png"'
return response
Django also provides FileResponse for file-like binary responses. For an in-memory buffer, rewind it before passing it to FileResponse. See Django’s request and response documentation.
3. Validate and constrain requested URLs
A publicly accessible screenshot view that accepts arbitrary URLs can be abused to make your server request destinations that your application did not intend to expose. Scheme checking alone is not a complete production URL policy. Authenticate the route if it is private, apply rate limits, and allow only expected hosts when your use case permits it. If arbitrary public URLs are a requirement, define and enforce a deliberate outbound request policy for your environment.
For a fixed internal use case, an allowlist can be simple:
from urllib.parse import urlparse
ALLOWED_HOSTS = {"example.com", "www.example.com"}
def is_allowed_target(target):
parsed = urlparse(target)
return (
parsed.scheme in {"http", "https"}
and parsed.hostname in ALLOWED_HOSTS
and parsed.username is None
and parsed.password is None
)
Call is_allowed_target before contacting ApiFlash and return HTTP 400 for disallowed input. In production, decide whether subdomains are allowed and normalize internationalized hostnames consistently. Do not assume a syntactically valid URL is an authorized target.
4. Choose image bytes or JSON links
The default API result is binary image data, which is the simplest fit when Django should display or download a capture immediately. If your application needs a screenshot URL or extraction results, use ApiFlash’s response_type=json option and parse the JSON response instead. Do not pass JSON bytes back with an image content type.
ApiFlash supports both GET query parameters and POST form data. The view above uses GET. POST can be useful when you prefer capture parameters in a form body, but the access key remains a credential and must stay server-side either way.
5. Configure capture behavior
ApiFlash’s documented options let you control output format, viewport, readiness, caching, and page cleanup. Add only the parameters your use case needs; the exact encoded values still need to be sent as ordinary HTTP request parameters.
| Need | Parameter or behavior | Notes |
|---|---|---|
| Image format | format |
jpeg is the default; png and webp are also documented. quality applies to JPEG and WebP. |
| Viewport size | width, height |
Documented defaults are 1920 × 1080. ApiFlash constrains dimensions and total pixel area. |
| Entire page | full_page=true |
Captures the full page height; the height setting does not apply in this mode. |
| Wait for loading | wait_until |
Values include dom_loaded, page_loaded, and network_idle (documented default). wait_until_timeout sets a cap; capture may proceed with whatever loaded when the cap is reached. |
| Wait for a specific element | wait_for |
Useful when a CSS selector appears after initial load. ApiFlash says capture aborts if no match appears within 15 seconds. |
| Fixed extra wait | delay |
Optional delay up to 10 seconds. The docs recommend readiness controls where possible. |
| Freshness and cache | fresh, ttl |
Documented TTL default is 86,400 seconds and maximum is 2,592,000 seconds (30 days). Identical successful-parameter requests may use cache and not count toward monthly quota, according to ApiFlash docs. |
| Reduce page clutter | no_cookie_banners, no_ads, no_tracking |
Use the supported flags when overlays or page clutter should be removed. |
| Capture or customize content | element, crop, css, js |
Use these documented options for element-focused capture, cropping, or page modifications. |
| Control request context | user_agent, cookies, headers |
Values may contain reserved characters; let the HTTP client encode parameters rather than building a query string manually. |
For example, to capture a full page and wait for a page-specific element, extend the view’s parameter dictionary:
params.update({
"full_page": "true",
"wait_for": ".article-ready",
"no_cookie_banners": "true",
})
When passing cookies, headers, CSS, JavaScript, or a URL with its own query string, prefer a library’s parameter encoding rather than string concatenation. ApiFlash documents URL encoding as necessary for values that require it.
6. Handle errors, quotas, and monitoring
ApiFlash documents these status codes. Keep a safe public error for the Django caller and record enough redacted detail in server logs to diagnose the upstream response. Do not log the access key or blindly expose the upstream body and target URL.
| ApiFlash status | Likely meaning | Suggested action |
|---|---|---|
| 400 | Invalid parameters or target cannot be captured | Check the complete target URL, supported option names and values, and whether the target is reachable. |
| 401 | Invalid or revoked key | Check deployment configuration and rotate or replace the key if needed. |
| 402 | Plan quota exceeded | Inspect account quota and reduce unnecessary captures or caching misses. |
| 403 | Feature unavailable on the plan | Remove the restricted option or confirm current account entitlements. |
| 429 | Rate limited | Queue or slow requests and retry with backoff where appropriate. |
| 500 | Capture service failure | Retry selectively and retain a request identifier or redacted diagnostics if provided. |
ApiFlash documents a rate limit of 20 requests per second with a burst size of 400; excess requests may be delayed, and requests beyond the burst may receive 429. Repeated identical failed captures are limited to 5 requests per hour. Successful responses can include X-Quota-Limit, X-Quota-Remaining, and X-Quota-Reset. The documented quota endpoint is /v1/urltoimage/quota. Use the headers or endpoint for monitoring rather than hard-coding an allowance. ApiFlash does not publish uptime statistics in the reviewed FAQ, and these documented limits are not an uptime guarantee.
7. Performance, reliability, and cost choices
- Set timeouts. The sample uses a 5-second connect timeout and a 45-second read timeout. Tune these to your request budget and target sites; without timeouts, slow upstream work can tie up Django workers.
- Reuse captures when freshness allows. ApiFlash documents cached results for repeated identical successful parameters, with a default TTL of one day and maximum of 30 days. Set the cache policy to match how often the target changes, and use the documented freshness controls when a new capture is required.
- Avoid arbitrary sleeps. Prefer a load condition or selector for dynamic content. A fixed delay can waste time on fast pages and still be too short on slow ones.
- Keep large responses in mind. Full-page captures and large viewport dimensions can produce larger image bodies and longer requests. Return bytes directly for modest captures; for larger workloads, consider a background job and object storage in your application architecture.
- Do not assume quota or current pricing. The reviewed research establishes quota-related responses and headers, but not current plan prices or tier allowances. Check current ApiFlash account information before making cost estimates.
- Retry selectively. A transient connection or service failure may merit bounded retry with backoff. Do not retry invalid input, invalid credentials, unsupported plan features, or quota exhaustion as though they were transient.
8. Troubleshooting
Django returns 400 before contacting ApiFlash
The submitted URL is missing or does not begin with http:// or https://. Submit a complete URL and confirm your application’s host policy allows it.
ApiFlash returns 401
The configured key may be absent, mistyped, invalid, or revoked. Verify the environment variable in the running Django process and avoid putting the key in the request URL used by the browser.
ApiFlash returns 402 or 403
A 402 indicates exhausted quota; a 403 indicates a plan-restricted feature. Check quota headers or the quota endpoint, then verify current plan entitlements before using restricted options.
ApiFlash returns 429
Your request rate exceeded the documented service limit or burst. Add a queue or concurrency limit and use bounded exponential backoff for eligible requests. Avoid retry storms.
The page is blank or missing dynamic content
Choose an appropriate wait_until condition or wait for a stable CSS selector with wait_for. If content is rendered after an interaction or requires authentication, ensure the target’s supported cookies, headers, or other capture settings are configured.
Django reports an unexpected response type
The upstream response may be an API error or JSON rather than an image. Check the upstream status before content type, and confirm that you did not request response_type=json while returning it as an image.
Captures take too long or Django workers are tied up
Reduce unnecessary full-page work, use a suitable wait condition, set a bounded timeout, and move long-running or high-volume capture workflows into an application queue.
The target URL contains query parameters or special characters
Pass it through requests.get(..., params=params) as shown. Hand-built query strings can corrupt nested URL parameters or credentials through incorrect encoding.
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; see the ScreenshotNeo API documentation for its parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
- Cookie banners, popups, and chat widgets are removed before the shot, and each cleanup step can be turned off.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers say which outcome occurred.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Does ApiFlash provide a Django package?
The reviewed official documentation describes its HTTP API, not a Django-specific package. A Django view can call that API with a standard HTTP client such as requests.
Can I show the screenshot directly in a page?
Yes. Return the image bytes with the upstream image content type and use the Django route as an image source, or set an attachment disposition when the user should download it.
Can I capture a page without exposing the API key?
Yes. Make the ApiFlash request from Django and keep the key in server configuration. Do not call ApiFlash directly from browser code.
Does a full-page capture use the viewport height?
No. ApiFlash documents that full_page=true captures the entire page and makes the height option inapplicable.


