Capturing a website screenshot programmatically is a common requirement for monitoring dashboards, link preview generators, SEO tools, and testing pipelines. In this guide, you'll learn how to take a screenshot of any website using Python and the Site-Shot API.
There are two ways to do it, and both are here. Site-Shot ships an official Python SDK — pip install site-shot — a typed client with zero dependencies. If you'd rather not add a dependency at all, the API is one HTTP GET, so requests is enough.
Prerequisites
- Python 3.9+ for the SDK (
pip install site-shot), or any Python 3 withrequestsfor the plain-HTTP route - A Site-Shot API key — API plans start at $5/mo for 2,000 screenshots (pricing). The free browser tool at site-shot.com needs no signup and no key, but it's a browser tool, not an API tier
The Official Python SDK
pip install site-shot
from site_shot import SiteShot
client = SiteShot("YOUR_API_KEY") # or set SITESHOT_API_KEY in the environment
png = client.capture("https://example.com/", full_size=True)
with open("screenshot.png", "wb") as f:
f.write(png)
The PyPI distribution is site-shot and the import package is site_shot — the two spellings are never swapped. The client is standard library only (zero dependencies), needs Python 3.9 or newer, ships type hints, and is synchronous. The target URL is positional; every capture option is a keyword argument. Source and issues live at github.com/site-shot/site-shot-python.
Four Return Modes
One capture concept, four methods — you pick the return type instead of passing a flag:
png = client.capture("https://example.com/") # bytes
client.capture_to_file("https://example.com/", "shot.png") # writes the file, returns the path
b64 = client.capture_base64("https://example.com/") # str, data-URL prefix stripped
meta = client.capture_json("https://example.com/", source_code=True) # dict: image + metadata
A fifth method, build_url(), returns the request URL without sending it — useful when debugging a capture. It embeds your API key as userkey and Site-Shot has no signed-URL scheme, so keep that URL server-side: never put it in an <img src> attribute.
No Dependency: One Plain GET
The API is a single GET request, so the dependency-free route is still first-class:
import requests
response = requests.get("https://api.site-shot.com/", params={
"url": "https://example.com",
"userkey": "YOUR_API_KEY",
"width": 1280,
"height": 1024,
"format": "png",
}, timeout=70)
with open("screenshot.png", "wb") as f:
f.write(response.content)
That's it. The API renders the page in a real Chromium browser and returns the image bytes directly. Every keyword the SDK accepts is one of these query parameters, spelled the same way, so the reference table below serves both routes.
Full Page Screenshot
To capture the entire scrollable page rather than just the viewport, ask for full_size and cap the result with max_height (up to 20,000 pixels):
png = client.capture("https://example.com/", full_size=True, max_height=15000)
# plain HTTP: add "full_size": 1 and "max_height": 15000 to params
The API scrolls through the whole document before capturing, which also triggers lazy-loaded images.
Mobile Device Screenshot
There is no device preset — you set the viewport and the user agent yourself:
png = client.capture(
"https://example.com/",
width=375,
height=812,
user_agent="Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) "
"Version/16.0 Mobile/15E148 Safari/604.1",
)
Screenshot from a Specific Country
Pass a two-letter ISO 3166-1 country code and the API routes the render through an IP in that country, with a matching language, time zone, and geolocation:
from site_shot import SiteShot, CountryUnavailableError
client = SiteShot("YOUR_API_KEY")
try:
png = client.capture("https://example.com/", country="DE", strict_country=True)
except CountryUnavailableError:
... # no capacity in DE right now — retry later, or drop strict_country
Use ISO codes only — country="DE", not the country's full name, which is not a valid value and silently renders from the US instead. The same silent US fallback happens when the requested country has no capacity at that moment; strict_country turns that into an honest failure — country_unavailable in the API's JSON, CountryUnavailableError in the SDK. Country routing is included with any paid plan.
JSON, Metadata and Rendered HTML
When you need more than the image — the target's HTTP status, its response headers, or the HTML as rendered — ask for the JSON result:
meta = client.capture_json("https://example.com/", source_code=True)
print(meta["response"]["status_code"]) # 200
print(meta["response"]["headers"]) # [{"name": ..., "value": ...}, ...]
print(meta["source_code"][:200]) # rendered HTML
On the plain-HTTP route the same envelope comes back from response_type=json, and the image arrives as a data URL you strip and decode yourself:
import base64
import requests
response = requests.get("https://api.site-shot.com/", params={
"url": "https://example.com",
"userkey": "YOUR_API_KEY",
"response_type": "json",
}, timeout=70)
payload = response.json()
if payload.get("error"):
raise RuntimeError(payload["error"])
with open("screenshot.png", "wb") as f:
f.write(base64.b64decode(payload["image"].split(",", 1)[-1]))
The envelope carries image (a base64 data URL), response (the target's status_code and headers), screenshot_parameters (every value the renderer actually used), and source_code when you ask for it. A failed capture arrives in-band as a JSON body with an error field — country_unavailable, for instance — which is why that check comes before the decode. The SDK runs the check for you and raises instead: AuthError, QuotaError, InvalidParamsError, CountryUnavailableError, SiteShotTimeoutError or APIError, all subclasses of SiteShotError.
Batch Screenshots
To capture multiple URLs, loop through them:
urls = ["https://example.com", "https://wikipedia.org", "https://github.com"]
for url in urls:
filename = url.replace("https://", "").replace("/", "_") + ".png"
client.capture_to_file(url, filename, width=1280, height=1024)
print(f"Saved {filename}")
For higher throughput, move that call into a concurrent.futures.ThreadPoolExecutor. The client holds no pooled connections, so one instance can be shared across threads; the API supports concurrent requests based on your plan's dedicated worker count.
Where the honest limits are
- Two languages have an SDK. Node.js (
npm install site-shot-sdk) and Python (pip install site-shot). In Go, Ruby, Java, C# or PHP you call the same HTTP endpoint directly — the plain-GET section above is the whole integration. - The Python client is synchronous. There is no async client, so in asyncio code run a capture in a worker thread (
asyncio.to_thread). - The API always needs a key. The no-signup path is the browser tool, not the API.
- Images only. Site-Shot returns PNG or JPEG — no PDF and no video output.
Parameter Reference
The SDK takes booleans where the query string takes 1/0.
| Parameter | Description | Example |
|---|---|---|
url |
Target web page (positional in the SDK) | https://example.com |
userkey |
Your API key (SDK: constructor argument) | abc123 |
width |
Viewport width (100–8000) | 1280 |
height |
Viewport height (100–20000) | 1024 |
full_size |
Capture full page | True / 1 |
max_height |
Height cap for full-page captures | 15000 |
format |
Output format | png or jpeg |
delay_time |
Wait before capture (ms) | 2000 |
response_type |
Response format (SDK: pick the method) | image or json |
source_code |
Include the rendered HTML | True / 1 |
country |
Proxy country (two-letter ISO code) | DE |
strict_country |
Fail instead of falling back to the US | True / 1 |
no_ads / no_cookie_popup |
Strip ads / cookie banners | True / 1 |
Check the full API documentation for all available parameters. For scheduled captures without code, see automatic daily screenshots; for the viewport-vs-full-page decision, see full page vs viewport screenshots; for country routing in depth, see screenshots from another country.
FAQ
Is there an official Python SDK for Site-Shot?
Yes. pip install site-shot installs the official client (the import package is site_shot): zero dependencies, Python 3.9 and newer, type hints included, synchronous. It wraps the same HTTP API — client.capture(url, full_size=True) returns PNG bytes, and capture_to_file(), capture_base64() and capture_json() cover the other return modes. Node.js has an official SDK too (npm install site-shot-sdk); in every other language you call the HTTP API directly.
Do I need an API key to take website screenshots with Python?
For the Site-Shot API, yes — every request carries your userkey (the SDK takes it in the constructor and sends it for you), and keys come with any paid plan (from $5/mo for 2,000 screenshots). For a quick one-off capture without code, the free in-browser tool at site-shot.com needs no signup and no key at all.
How do I capture a full-page screenshot in Python?
Pass full_size=True to the SDK, or add full_size=1 to a plain HTTP request, and cap the height with max_height (up to 20,000 pixels). The API scrolls the whole document — triggering lazy-loaded content — and returns one tall image.
Can Python capture a screenshot as seen from another country?
Yes. Pass country with a two-letter ISO code (for example country=DE) and the API routes the render through a real IP in that country, with matching language, time zone, and geolocation. Country routing is included with any paid plan.
Capture your first screenshot free in your browser — no signup — at site-shot.com, or compare API plans on the pricing page.