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

FastAPI does not render a web page itself. To return a screenshot, run a real browser such as Chromium through Playwright, navigate to the submitted HTTP(S) URL, capture the page as bytes, and send those bytes with FastAPI’s StreamingResponse. The example below is a complete asynchronous endpoint with URL validation, a 30-second navigation limit, viewport and full-page modes, and guaranteed browser cleanup.

What you need

Install FastAPI, an ASGI server, the Playwright Python package, and the browser binary that Playwright controls:

python -m pip install fastapi uvicorn playwright
python -m playwright install chromium

Run the application with uvicorn app:app --reload when the file is named app.py. The browser installation is a separate step; installing the Python package alone does not install Chromium.

Build the FastAPI screenshot endpoint

Create app.py with this implementation:

from io import BytesIO
from urllib.parse import urlparse

from fastapi import FastAPI, HTTPException, Query
from fastapi.responses import StreamingResponse
from playwright.async_api import TimeoutError as PlaywrightTimeoutError
from playwright.async_api import async_playwright

app = FastAPI()


def is_http_url(value: str) -> bool:
    parsed = urlparse(value)
    return parsed.scheme in {'http', 'https'} and bool(parsed.netloc)


@app.get('/screenshot')
async def screenshot(
    url: str = Query(..., description='HTTP or HTTPS page to capture'),
    full_page: bool = False,
):
    if not is_http_url(url):
        raise HTTPException(status_code=400, detail='Only HTTP(S) URLs are allowed')

    async with async_playwright() as playwright:
        browser = await playwright.chromium.launch()
        page = await browser.new_page(viewport={'width': 1440, 'height': 900})
        try:
            await page.goto(url, wait_until='networkidle', timeout=30_000)
            image_bytes = await page.screenshot(type='png', full_page=full_page)
        except PlaywrightTimeoutError:
            raise HTTPException(status_code=504, detail='Page load timed out')
        finally:
            await browser.close()

    return StreamingResponse(BytesIO(image_bytes), media_type='image/png')

The route rejects non-HTTP(S) values before launching a browser. page.goto waits for network activity to settle, then page.screenshot returns PNG bytes in memory. No temporary image file is needed. The finally block closes Chromium when navigation or capture raises an exception.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
Elebase USB to USB C Adapter for iPhone 18 Pro Max,USBC Car Charger Adapter
  • Read Before You Buy — No Video Output: These adapters support charging and USB 2.0 data transfer, but cannot transmit video signals. Except for standard USB webcams (which use USB data only), they are not compatible with HDMI/DisplayPort cables, video-capable USB-C hubs, or docking stations with video output.
  • Convert USB-A Ports to USB-C: Designed to connect USB-C earphones, cables, flash drives, card readers, and other USB-C accessories to standard USB-A ports. Plug-and-play with no drivers or software required.
  • Aluminum Alloy Housing: Built with a sturdy aluminum alloy shell that aids in heat dissipation and protects against daily wear and scratches. Designed to maintain a stable and secure connection.
  • Compact & Travel-Friendly: The ultra-compact design allows the adapter to stay plugged into your device without blocking adjacent ports or adding bulk, reducing wear and tear on your original USB ports.
  • 12-Month Warranty: Backed by a 12-month manufacturer warranty for peace of mind. Designed to meet strict quality control standards for reliable everyday performance.

Call the local route

Encode the target URL as a query parameter. The full_page value is optional and defaults to the current viewport:

curl -G 'http://127.0.0.1:8000/screenshot' 
  --data-urlencode 'url=https://example.com' 
  -o example.png

curl -G 'http://127.0.0.1:8000/screenshot' 
  --data-urlencode 'url=https://example.com' 
  --data 'full_page=true' 
  -o example-full.png

A browser, command-line client, or another service receives an image response with the image/png media type.

Viewport, full-page, element, and region captures

Viewport versus full page

With full_page=False, Playwright captures the visible 1,440 by 900 CSS-pixel viewport configured in the example. With full_page=True, it captures the entire scrollable document. A very long page can therefore produce a large image and take longer to rasterize. Pages that load content only after scrolling may require additional scrolling or explicit waits before the capture.

JPEG and WebP output

Playwright supports PNG, JPEG, and WebP screenshot output. Replace the call with, for example:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
image_bytes = await page.screenshot(
    type='jpeg',
    quality=85,
    full_page=full_page,
)

JPEG quality applies to JPEG output; PNG is lossless and has no quality parameter. Return the matching media type, such as image/jpeg or image/webp, instead of always declaring PNG.

Rank #2
Anker USB-C Hub, 5-in-1 USB Hub for Laptops, 4K HDMI Multiport Adapter
  • 5-in-1 USB-C Hub: Experience comprehensive connectivity featuring a Power Delivery input, two USB-A 2.0 ports, a USB-A 3.0 port, and an HDMI port. (Note: The USB-C power delivery input port is only for connecting an external wall charger to power your laptop and cannot power peripheral devices.)
  • 90W Pass-Through Charging: Achieve optimal charging with 90W pass-through power to your laptop, supported by a total input of 100W, with the hub reserving 10W for operational efficiency. (Note: Wall charger not included.)
  • Quick Data Transfers: Accelerate your productivity with rapid data transfers using a high-speed 5Gbps USB 3.0 port and two 480Mbps USB 2.0 ports.
  • 4K HDMI Display: Enhance your visual experience with a hub capable of delivering 4K resolution at 30Hz in both mirror and extend modes. Please note that this hub is compatible with MacBook (macOS 12 and newer), Windows 10 and 11, ChromeOS, and laptops equipped with DP Alt Mode and Power Delivery. Note: This device is not compatible with Linux.
  • What You Get: Anker USB-C Hub (5-in-1, 4K HDMI), welcome guide, 18-month warranty, and our friendly customer service.

Capture one element

Use a locator when the response should contain a component rather than the whole page:

image_bytes = await page.locator('#invoice').screenshot(type='png')

The locator must resolve to a visible element. A missing selector raises an error, so production code should catch that condition and return a useful 4xx response if the selector is supplied by a caller.

Capture a rectangular region

A clip rectangle uses page coordinates and dimensions:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
image_bytes = await page.screenshot(
    type='png',
    clip={'x': 100, 'y': 120, 'width': 800, 'height': 500},
)

Coordinates outside the rendered page, negative dimensions, or a region that is not available can cause capture failures. Validate caller-provided numbers and impose maximum dimensions.

Control device-pixel resolution

The scale option controls whether output uses CSS pixels or device pixels. Use scale='device' for a higher-density image, or scale='css' for output closer to the CSS viewport dimensions. You can also set a device scale factor when creating the browser context if your design requires a particular device profile.

Rank #3
Sale
Anker USB C Hub, 7in1 Multi-Port USB Adapter, 4K@60Hz USBC to HDMI Splitter
  • Sleek 7-in-1 USB-C Hub: Features an HDMI port, two USB-A 3.0 ports, and a USB-C data port, each providing 5Gbps transfer speeds. It also includes a USB-C PD input port for charging up to 100W and dual SD and TF card slots, all in a compact design.
  • Flawless 4K@60Hz Video with HDMI: Delivers exceptional clarity and smoothness with its 4K@60Hz HDMI port, making it ideal for high-definition presentations and entertainment. (Note: Only the HDMI port supports video projection; the USB-C port is for data transfer only.)
  • Double Up on Efficiency: The two USB-A 3.0 ports and a USB-C port support a fast 5Gbps data rate, significantly boosting your transfer speeds and improving productivity.
  • Fast and Reliable 85W Charging: Offers high-capacity, speedy charging for laptops up to 85W, so you spend less time tethered to an outlet and more time being productive.
  • What You Get: Anker USB-C Hub (7-in-1), welcome guide, 18-month warranty, and our friendly customer service.

Waiting for dynamic pages

wait_until='networkidle' is convenient for ordinary pages but can delay indefinitely on sites with analytics, advertisements, WebSockets, or polling. Choose a condition that represents readiness for your page:

  • Use wait_until='domcontentloaded' when the initial DOM is sufficient.
  • Navigate first, then call await page.wait_for_selector('#report', timeout=10_000) for a known application landmark.
  • Use a short, explicit delay only when an animation or client-side render has no reliable selector.
  • For lazy-loaded images, scroll the page or wait for the specific images before capturing.

Do not remove the timeout. A remote server that never finishes loading should produce a controlled error instead of occupying a worker forever.

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

Return a file instead of bytes

The screenshot API returns bytes when path is omitted. For a batch job or an artifact pipeline, provide a path instead:

await page.screenshot(path='artifacts/home.png', type='png', full_page=True)

When serving the result from FastAPI, keeping the bytes in memory avoids a temporary-file cleanup race. For very large captures, writing to managed storage and returning a download URL can reduce memory pressure, but that introduces storage permissions, retention, and access-control decisions.

Secure a production capture route

The validation in the example checks syntax, not ownership or network safety. A public screenshot endpoint is also a server-side request proxy, so add controls appropriate to your deployment:

Rank #4
Sale
UGREEN USB to USB C Adapter Combo 4-Pack, 10Gbps USB C Converter Space Gray
  • Dual Converters, Infinite Potential:Includes 2× USB C male to USB A female adapters and 2× USB A male to USB C female adapters. Perfect for a wide range of uses—tablets with Bluetooth keyboards, expand USB ports on macbook, and more. Two different converters for all your daily needs
  • Next-Level 10Gbps & 3A Charging: No more slow 480Mbps, this usb to usb c adapter has a transfer speed of up to 10Gbps, allowing you to do more transferring in less time. This usb adapter fits both USB A and USB C charger, supporting up to 3A fast charging
  • Upgraded Exquisite Craftsmanship: With an aluminum alloy housing and metal connector, the usbc to usb adapter is extremely durable and sturdy. Rigorously tested to withstand more than 10,000 times of plugging and unplugging, ensuring long-lasting performance
  • Broad Compatible: The usb c to usb adapter widely supports all USB C/ USB A devices like laptops, tablets, cellphones, car chargers, and phone chargers. Such as compatible with MacBook Pro/Air 2023/2022, Thunderbolt 4/3 Devices,Apple MagSafe Watch 9/8/7/SE/Ultra, iPad Pro 2022/2021, Samsung Galaxy S23/S20/S10, and iPhone 17/16/15 Pro. Plug and play
  • Please Note: To reach 10Gbps speed, keep the cable under 3.3 ft. For USB A Male to USB C adapters, try flipping the USB C connector. USB C Male to USB A adapters support bidirectional 10Gbps transfer within 3.3 ft
  • Allowlist permitted hostnames when callers should capture only your own sites.
  • Block loopback, link-local, private, and metadata-service addresses, including DNS names that resolve to them. Re-check the resolved destination to reduce DNS-rebinding risk.
  • Require authentication, rate-limit requests, and cap the maximum URL length.
  • Limit viewport dimensions, full-page height, output bytes, and navigation time.
  • Use an asyncio.Semaphore or a worker queue to cap concurrent browser work.
  • Run Chromium with an appropriate sandbox and a restricted service account; do not grant the capture process unnecessary filesystem or network access.

There is no universal concurrency or dimension limit. Measure memory and latency in your own deployment, then set limits that leave headroom for other FastAPI requests.

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

Improve cold-start and reliability

Launching Chromium for every request is simple and isolates failures, but browser startup adds latency and CPU cost. A long-lived application can launch one browser process during startup, create a fresh browser context or page for each request, and close those isolated objects in a finally block. Contexts keep cookies, local storage, viewport settings, and permissions from leaking between users. If the browser process crashes, detect it and recreate it rather than reusing a dead handle.

Use retries carefully. Retrying a timeout against a slow origin can multiply load and queue time. A bounded retry for a transient browser-process failure is safer than blindly repeating every navigation error. Log the target host, elapsed navigation and capture times, output dimensions, and the exception class, but avoid logging credentials or sensitive query strings.

Common errors and fixes

Symptom Likely cause Fix
HTTP 400, “Only HTTP(S) URLs are allowed” The query value is missing a scheme, has an invalid hostname, or uses a non-web protocol. Send a complete http:// or https:// URL and URL-encode it. Add an allowlist if only specific hosts are valid.
HTTP 504, “Page load timed out” The origin is slow, unreachable, or never reaches the selected readiness state. Check the URL from the server, choose a more suitable wait_until condition, wait for a specific selector, or increase the timeout within an enforced maximum.
Executable missing when launching Chromium The Playwright package is installed but its browser binary is not. Run python -m playwright install chromium in the same environment used by Uvicorn, and ensure the deployment image includes the binary.
Blank or partially rendered image Capture occurred before client-side rendering or lazy assets completed. Wait for an application selector, scroll to trigger lazy loading, or use a short bounded delay after navigation.
Element screenshot fails The selector matches nothing, is hidden, or is outside a usable state. Verify the selector in the target page, wait for it, and return a clear client error when caller-supplied selectors are invalid.
High memory use or queued requests Too many concurrent full-page browser captures or unbounded page sizes. Use a semaphore or queue, cap dimensions and timeouts, and reuse a browser process with isolated contexts.
Internal services become reachable The endpoint accepts arbitrary URLs and acts as an SSRF proxy. Block private and metadata networks, enforce an allowlist where possible, and authenticate the route.
Client cannot display the result The response media type does not match the bytes. Set image/png, image/jpeg, or image/webp to match the selected screenshot type.

DIY Playwright or a hosted screenshot API?

Running Playwright in your FastAPI service gives you direct control over browsers, authentication, custom waits, and network policy. It also makes you responsible for browser binaries, scaling, isolation, upgrades, and failure handling. A hosted API is useful when you want an HTTP response without maintaining that browser fleet.

Approach Best fit Main responsibility
ScreenshotNeo FastAPI or other services that need a managed screenshot request; it is the first hosted option to try because it removes consent UI, bills only clean captures, and has a $5 paid plan. Send the URL and options; the service handles browser execution.
Self-hosted Playwright Teams needing custom browser behavior, private network access, or complete control. Operate Chromium, concurrency, security, and scaling.
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 website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. The service accepts cookie and consent banners as a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—let Claude, Cursor, or another MCP client request captures.

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

See the ScreenshotNeo API documentation for all options. This is the one-call example:

Best Value
Anker USB C Hub, 5-in-1 USBC to HDMI Splitter with 4K Display
  • 5-in-1 Connectivity: Equipped with a 4K HDMI port, a 5 Gbps USB-C data port, two 5 Gbps USB-A ports, and a USB C 100W PD-IN port. Note: The USB C 100W PD-IN port supports only charging and does not support data transfer devices such as headphones or speakers.
  • Powerful Pass-Through Charging: Supports up to 85W pass-through charging so you can power up your laptop while you use the hub. Note: Pass-through charging requires a charger (not included). Note: To achieve full power for iPad, we recommend using a 45W wall charger.
  • Transfer Files in Seconds: Move files to and from your laptop at speeds of up to 5 Gbps via the USB-C and USB-A data ports. Note: The USB C 5Gbps Data port does not support video output.
  • HD Display: Connect to the HDMI port to stream or mirror content to an external monitor in resolutions of up to 4K@30Hz. Note: The USB-C ports do not support video output.
  • What You Get: Anker 332 USB-C Hub (5-in-1), welcome guide, our worry-free 18-month warranty, and friendly customer service.
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}`);

Every plan includes the full feature set: full-page and element captures, dark mode, device presets or custom viewports, retina scale, PDF page controls, HTML/CSS rendering, custom JavaScript and CSS, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, usage reporting, and an OpenAPI specification. Parameter names used by other screenshot APIs also work.

The Free plan includes 1,000 screenshots each month without a card. Paid plans are Starter at $5 for 3,000 shots, Growth at $15 for 15,000, Pro at $39 for 60,000, Scale at $99 for 250,000, and Business at $249 for 1,000,000; yearly billing provides two months free. Create a free ScreenshotNeo account to start with 1,000 screenshots a month and no card.

FastAPI client examples

Python

import requests

response = requests.get(
    'http://127.0.0.1:8000/screenshot',
    params={'url': 'https://example.com', 'full_page': 'true'},
    timeout=90,
)
response.raise_for_status()
with open('page.png', 'wb') as output:
    output.write(response.content)

Node.js

const params = new URLSearchParams({
  url: 'https://example.com',
  full_page: 'true'
});
const response = await fetch(`http://127.0.0.1:8000/screenshot?${params}`);
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const image = Buffer.from(await response.arrayBuffer());
await require('node:fs').promises.writeFile('page.png', image);

Key decisions before shipping

  • Decide whether callers need the viewport, the full scrollable document, an element, or a clip rectangle.
  • Choose PNG for lossless output or JPEG/WebP when smaller files matter, and return the corresponding media type.
  • Define a readiness condition for dynamic pages instead of relying on an unlimited wait.
  • Set authentication, host restrictions, private-network blocks, concurrency limits, and output caps before exposing the route publicly.
  • Reuse a browser process with isolated contexts when startup latency matters; retain per-request cleanup even in that design.
  • Use ScreenshotNeo when maintaining browser infrastructure is not part of your service’s job.

Frequently Asked Questions

Can the same FastAPI route return a PDF?

Yes, but PDF generation is a separate Chromium operation. Use Playwright’s PDF API in a route that returns application/pdf; do not label PDF bytes as an image.

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

Should I use Playwright’s synchronous API in an async FastAPI handler?

Prefer Playwright’s asynchronous Python API shown here. Synchronous browser calls can block the event loop and delay unrelated FastAPI requests unless isolated in a worker thread or process.

How can I capture a page that requires login?

Create a browser context with the required authentication state or set cookies and headers before navigation. Keep credentials server-side, isolate contexts between requests, and never accept arbitrary credential values from an unauthenticated caller.

What happens if the target page rejects automated browsers?

Playwright may receive a challenge, an empty document, or an error page. Treat that as an origin-specific failure; do not attempt to bypass access controls, and consider a permitted integration or a service that reports the failed verdict explicitly.

The Bottom Line

Use asynchronous Playwright inside FastAPI, validate and restrict destination URLs, wait for the page state your application needs, capture bytes, and stream them with the correct image media type. For a managed alternative that removes consent UI and charges only for clean captures, try ScreenshotNeo.

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

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.