Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

To capture a web page from Django, send a server-side HTTP request to a hosted screenshot API, then return the image or PDF response from a Django view. The example below uses the documented POST /api/v1/screenshot contract with bearer authentication, JSON options, full-page capture and a configurable viewport. Keep the API key on your server; never put it in browser JavaScript.

Choose the capture approach first

Approach Best for Request and output
Hosted screenshot API Application features, reports, scheduled jobs and capturing public URLs GET query parameters or POST JSON; PNG, JPEG, WebP or PDF
Official Python SDK Teams that prefer a package abstraction over HTTP Install with pip install screenshot-api; the provider documents Django, Flask and FastAPI compatibility
Django Selenium screenshots Visual regression and browser-based tests against your own application Local test browser controlled by SeleniumTestCase and Django’s screenshot test options

Use a hosted API when your Django application needs to capture a URL as part of normal runtime work. Use Selenium when the purpose is testing your interface in a controlled browser. They solve related but different problems.

Quick start: a Django view with the REST API

1. Install the HTTP client

pip install requests

The provider also lists an official SDK installable with pip install screenshot-api. Direct HTTP is shown here because it exposes the documented request and response contract without inventing SDK method names.

2. Put the key in server configuration

# settings.py
import os

SCREENSHOT_API_KEY = os.environ["SCREENSHOT_API_KEY"]

Set SCREENSHOT_API_KEY in your process environment or secret manager. The API reference recommends an authorization header. Do not render the key into templates, send it to a browser, or commit it to source control.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Create the view

# views.py
import requests
from django.conf import settings
from django.http import HttpResponse, JsonResponse


def screenshot(request):
    target_url = request.GET.get("url", "https://example.com")
    payload = {
        "url": target_url,
        "format": "png",
        "fullPage": True,
        "viewport": {"width": 1280, "height": 720},
    }

    try:
        response = requests.post(
            "https://api.screenshot-api.org/api/v1/screenshot",
            headers={
                "Authorization": f"Bearer {settings.SCREENSHOT_API_KEY}",
                "Content-Type": "application/json",
            },
            json=payload,
            timeout=60,
        )
    except requests.RequestException as exc:
        return JsonResponse({"error": "Screenshot service unavailable", "detail": str(exc)}, status=502)

    if not response.ok:
        return JsonResponse({"error": response.text}, status=response.status_code)

    return HttpResponse(
        response.content,
        content_type=response.headers.get("Content-Type", "image/png"),
    )

This view is an adaptation of the provider’s documented HTTP contract, not a provider-tested Django snippet. The endpoint, bearer header, JSON body, URL, format, full-page flag and viewport fields are the documented concepts; timeout and error handling are application choices.

4. Wire the URL

# urls.py
from django.urls import path
from .views import screenshot

urlpatterns = [
    path("screenshot/", screenshot, name="screenshot"),
]

Request /screenshot/ for the default page or /screenshot/?url=https%3A%2F%2Fexample.com for another URL. In production, authenticate this Django endpoint, rate-limit it and allow-list destinations. Accepting arbitrary user-supplied URLs can create a server-side request forgery risk, including access to private network addresses.

GET, POST and capture options

When GET is sufficient

The API documents GET requests for simple query-parameter calls. They are convenient for a single URL and a few basic settings, but query strings become difficult to audit when options grow.

Why POST is usually better in Django

POST sends a structured JSON body and is the documented choice for advanced controls. The required url identifies the page. format accepts PNG, JPEG, WebP or PDF. viewport.width and viewport.height set the browser viewport, while fullPage: true captures content beyond the initial viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
payload = {
    "url": "https://example.com/pricing",
    "format": "webp",
    "fullPage": False,
    "viewport": {"width": 1440, "height": 900},
}

The reference also lists POST-only controls for custom CSS, JavaScript, hidden selectors, geolocation and PDF-related settings such as paper configuration and output ranges. Consult the provider’s endpoint documentation before relying on a particular field name or value.

PDF responses

Set format to pdf and pass the returned bytes through with the response’s PDF content type. PDF-specific options are available through the documented POST controls; keep the response handling binary rather than decoding it as text.

Batch capture

For multiple pages, use the documented batch endpoint: https://api.screenshot-api.org/api/v1/screenshot/batch. Batch jobs can reduce application-side request orchestration, but validate every URL and decide how partial failures should be represented to your users.

Use the Python SDK or direct HTTP?

The provider’s official package is installed with pip install screenshot-api and is documented as working with Django, Flask and FastAPI. Choose it when its supported methods match your needs and you want dependency-level abstraction. Choose requests (or another HTTP client) when you need transparent control over the endpoint, headers, payload, timeout and error mapping. The available SDK material does not publish a complete Django method signature, so do not copy an invented call pattern; follow the package’s current documentation and inspect its returned response type.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Production hardening

  • Validate destinations: accept only approved schemes and hosts, resolve DNS safely and block localhost, link-local, loopback and private-address targets unless your architecture explicitly requires them.
  • Protect the view: require user authentication where appropriate, apply per-user rate limits and cap the requested page size or frequency.
  • Set bounded timeouts: use a connect/read timeout that fits your request budget. Return a controlled 502 or 504 rather than holding a worker indefinitely.
  • Preserve content types: use the service’s response header when returning PNG, JPEG, WebP or PDF bytes.
  • Control caching: cache captures whose source URL and options are identical; invalidate when the page changes.
  • Move slow work off the request path: queue long captures with your task system and store the resulting bytes in object storage, returning a job status to the client.
  • Log safely: record status, duration and a normalized host, but never log the bearer key or sensitive query parameters.

Common failures and fixes

401 or 403 response

Check that the environment variable is present in the web process (not only your shell), that the value is current and that the header is exactly Authorization: Bearer YOUR_KEY. Restart the process after changing configuration.

400 validation error

Confirm that url is present and absolute, format is one of PNG, JPEG, WebP or PDF, and viewport values are numbers. For complex options, send JSON with POST instead of encoding nested data into a GET query string.

Timeouts or blank captures

Verify that the target is reachable from the provider, not protected by an interactive login, and that the page finishes loading within your timeout. Try a smaller page or viewport, then inspect the provider’s error body. A Django request timeout does not make the remote page load faster; use background jobs for slow pages.

Image displayed as text or downloaded incorrectly

Return response.content, not response.text, and set the upstream Content-Type. PDFs must be treated as binary data as well.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unexpected SSRF exposure

Do not pass an unrestricted ?url= parameter to an internet-facing view. Enforce an allow-list or accept an internal object identifier that maps to a known URL.

Django Selenium screenshots for tests

Django’s own browser-testing workflow is the better fit for regression evidence. Its documentation covers SeleniumTestCase, the --screenshots test-runner option, @screenshot_cases(...) and self.take_screenshot("name"). Documented cases include desktop, mobile, small-screen, right-to-left, dark and high-contrast variants.

This captures the local test browser and exercises your application in a test context. A hosted API instead captures a URL through an external service and is suitable when screenshots are an application output. Select based on purpose, not just implementation language.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF, and its Django integration needs no Selenium installation:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for request options. It removes cookie-consent banners, newsletter popups and chat widgets before capture; bot checks, blank pages, failed loads, timeouts and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Can a browser call the screenshot API directly?

It should not when authentication uses a secret API key. Keep the request in Django and expose only your own controlled endpoint.

Should I return the bytes or save a file?

Return bytes for an immediate download or image response. Save to object storage when captures are reused, large, asynchronous or part of a report pipeline.

Is full-page capture the same as a tall viewport?

No. A viewport sets the browser’s visible dimensions; fullPage asks the service to include content beyond that initial viewport.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I authenticate the hosted API with a query-string key?

The documented recommendation is an Authorization bearer header. Follow that method so credentials are less likely to leak through URLs, logs or referrers.

Which method is easier to troubleshoot, GET or POST?

GET is quickest for a URL and basic parameters. POST is easier to inspect and extend because options remain structured JSON.

When should I choose Django Selenium instead?

Choose Selenium when you are testing your own application across documented browser variants; choose a hosted API when capture is a runtime feature or must target an external URL.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.