Official Python SDK for ScreenshotFreeAPI — screenshot-as-a-service
Project description
screenshotfreeapi Python SDK
Official Python SDK for ScreenshotFreeAPI — screenshot-as-a-service.
Capture pixel-perfect screenshots of any website, mobile app store listing, or raw HTML string with a single Python function call.
- Zero dependencies — pure Python stdlib (
urllib,json,hmac,hashlib) - Python 3.8+ compatible
- Synchronous API with built-in retry and job polling
- Full type annotations on all request/response objects
Installation
pip install screenshotfreeapi
Quick Start
from screenshotfreeapi import ScreenshotFreeAPIClient
client = ScreenshotFreeAPIClient(api_key="sfa_your_api_key_here")
result = client.capture("https://stripe.com")
print(result.screenshots[0].url)
Web Screenshots
Basic
result = client.capture("https://example.com")
print(result.screenshots[0].url) # pre-signed S3 URL
print(result.screenshots[0].width) # viewport width in pixels
AI-Targeted Sections
Describe the part of the page you want in plain English. ScreenshotFreeAPI uses Claude vision to find and crop exactly that element.
result = client.capture(
"https://stripe.com/pricing",
description="the pricing comparison table",
)
# metadata tells you what the AI did
print(result.metadata["aiConfidence"]) # e.g. 0.91
print(result.metadata["rawAiSelector"]) # e.g. ".pricing-table"
CSS Selector Targeting
result = client.capture(
"https://github.com",
element=".hero-section",
)
Full Page
result = client.capture("https://example.com", full_page=True)
PDF Generation
result = client.capture(
"https://example.com",
format="pdf",
paper_size="A4",
)
print(result.screenshots[0].url) # PDF download URL
All Options
result = client.capture(
"https://stripe.com/pricing",
description="the pricing table", # AI targeting
element=".pricing", # CSS selector crop
dimensions={"width": 1440, "height": 900},
full_page=False,
scrolling=False,
format="png", # "png" | "jpeg" | "webp" | "pdf"
paper_size="A4", # PDF only
block_ads=True,
accept_cookies=True,
stealth=False, # BUSINESS plan
proxy_location="us-east", # BUSINESS plan
bypass_cache=False,
webhook_url="https://your-app.com/webhook",
storage={ # BUSINESS plan — bring your own S3
"bucket": "my-bucket",
"region": "us-east-1",
"access_key_id": "AKIA...",
"secret_access_key": "...",
},
)
| Option | Type | Default | Description |
|---|---|---|---|
url |
str | required | Page URL to capture |
description |
str | None | Plain-English AI targeting description |
element |
str | None | CSS selector to crop to |
dimensions |
dict | 1280×720 | {"width": int, "height": int} |
full_page |
bool | False | Capture full scrollable height |
format |
str | "png" |
"png", "jpeg", "webp", or "pdf" |
paper_size |
str | "A4" |
PDF paper size |
block_ads |
bool | False | Strip ads (STARTER+) |
accept_cookies |
bool | False | Dismiss cookie banners (STARTER+) |
stealth |
bool | False | Stealth mode (BUSINESS+) |
proxy_location |
str | None | Geo-proxy region (BUSINESS+) |
bypass_cache |
bool | False | Skip cached result |
webhook_url |
str | None | Webhook URL for async notification |
storage |
dict | None | Custom S3 bucket (BUSINESS+) |
Mobile Screenshots
Capture App Store and Google Play listing images, or use device emulation.
# App Store + Google Play listing images
result = client.screenshots.mobile_and_wait({
"app_name": "Instagram",
"platform": "both", # "ios" | "android" | "both"
"include_store_listing": True,
"device_emulation": "iPhone 12",
})
for screenshot in result.screenshots:
print(screenshot.url)
# By bundle ID
result = client.screenshots.mobile_and_wait({
"bundle_id": "com.instagram.android",
"platform": "android",
})
HTML Rendering
Convert raw HTML strings (email templates, reports, certificates) to screenshots.
html = """
<html>
<body style="font-family:sans-serif; padding:40px">
<h1>Invoice #1042</h1>
<p>Total: <strong>$250.00</strong></p>
</body>
</html>
"""
result = client.screenshots.html_and_wait({
"html": html,
"dimensions": {"width": 800, "height": 600},
"format": "png",
})
print(result.screenshots[0].url)
Async Job Pattern
capture() and *_and_wait() are synchronous convenience wrappers that poll until the job finishes. For full control use the lower-level async pattern:
Manual enqueueing
# 1. Enqueue — returns immediately with a job ID
job = client.screenshots.web({"url": "https://example.com"})
print("Queued:", job.job_id)
print("Estimated:", job.estimated_seconds, "seconds")
# 2. Poll status manually
status = client.jobs.get_status(job.job_id)
print("Status:", status.status) # "queued" | "processing" | "completed" | "failed"
print("Progress:", status.progress, "%")
# 3. Fetch result once completed
if status.status == "completed":
result = client.jobs.get_result(job.job_id)
print("URL:", result.screenshots[0].url)
Using wait() with a progress callback
def show_progress(pct: int):
print(f" {pct}% complete", end="\r")
job = client.screenshots.web({"url": "https://stripe.com"})
result = client.screenshots.wait(
job.job_id,
poll_interval_ms=2000, # poll every 2 seconds
timeout_ms=120_000, # give up after 2 minutes
on_progress=show_progress,
)
print("\nDone:", result.screenshots[0].url)
Webhooks (server push)
Instead of polling you can pass webhook_url and receive a POST when the job completes:
job = client.screenshots.web({
"url": "https://example.com",
"webhook_url": "https://your-server.com/webhooks/screenshot-events",
})
# Your server receives: POST /webhooks/screenshot-events { "jobId": "...", "status": "completed", ... }
Webhook Verification
Verify that incoming webhooks were sent by ScreenshotFreeAPI and have not been tampered with.
from screenshotfreeapi.webhooks import verify_webhook_signature
# Inside your webhook handler (framework-agnostic):
raw_body: str = request.body # exact raw bytes decoded as UTF-8 string
signature: str = request.headers.get("X-ScreenshotFree-Signature", "")
secret: str = "your_webhook_secret"
if not verify_webhook_signature(raw_body, signature, secret):
raise Exception("Invalid webhook signature — reject request")
event = json.loads(raw_body)
print(event["status"]) # "completed" | "failed"
Error Handling
All errors inherit from ScreenshotFreeAPIError. Catch the most specific class you care about:
from screenshotfreeapi import (
ScreenshotFreeAPIClient,
AuthenticationError,
ForbiddenError,
NotFoundError,
ValidationError,
RateLimitError,
QuotaExceededError,
PaymentRequiredError,
JobFailedError,
JobTimeoutError,
ScreenshotFreeAPIError,
)
client = ScreenshotFreeAPIClient(api_key="sfa_...")
try:
result = client.capture("https://example.com")
except AuthenticationError:
# 401 — API key invalid or missing
print("Check your API key")
except ForbiddenError:
# 403 — key revoked or account suspended
print("Account suspended — contact support")
except PaymentRequiredError:
# 402 — subscription cancelled or payment failed
print("Renew your subscription")
except ValidationError as e:
# 400 — bad request parameters
print("Request error:", e.details)
except RateLimitError as e:
# 429 — too many requests per minute
import time
print(f"Rate limited, waiting {e.retry_after}s")
time.sleep(e.retry_after)
except QuotaExceededError:
# 429 with error=QuotaExceeded — monthly quota exhausted
print("Monthly quota used up — upgrade your plan")
except NotFoundError:
# 404 — job not found or not yours
print("Job not found")
except JobFailedError as e:
# Job reached FAILED status on the server
print(f"Job {e.job_id} failed: {e.reason}")
except JobTimeoutError as e:
# Polling timed out before job completed
print(f"Job {e.job_id} timed out after {e.timeout_ms}ms")
except ScreenshotFreeAPIError as e:
# Any other server-side error (5xx etc.)
print(f"API error {e.status_code}: {e.message}")
Error class reference
| Class | HTTP | When |
|---|---|---|
ScreenshotFreeAPIError |
any | Base class; also covers 5xx |
AuthenticationError |
401 | Invalid or missing API key / JWT |
ForbiddenError |
403 | Key revoked or account suspended |
PaymentRequiredError |
402 | Subscription cancelled or payment overdue |
NotFoundError |
404 | Resource not found or not owned by this key |
ValidationError |
400 | Request body failed validation; details has field errors |
RateLimitError |
429 | Per-minute rate limit hit; retry_after is seconds to wait |
QuotaExceededError |
429 | Monthly screenshot quota exhausted |
JobFailedError |
— | Job reached FAILED status; reason has the server message |
JobTimeoutError |
— | Polling timed out; timeout_ms is the configured limit |
Authentication
Register and get your API key
result = client.auth.register(
email="you@example.com",
password="strong-password-123!",
name="Your Name",
)
print("API key (save this!):", result.api_key)
Obtain a management JWT (for billing/workspaces/monitors)
token_result = client.auth.get_token(
email="you@example.com",
password="strong-password-123!",
)
jwt = token_result.token
print("Token expires:", token_result.expires_at)
Refresh a JWT
new_token = client.auth.refresh_token(token_result.refresh_token)
Billing
Billing endpoints require a management JWT (see above).
# List all available plans (no auth)
plans = client.billing.list_plans()
for plan in plans:
print(plan.name, plan.price_monthly, plan.screenshots_per_month)
# Current subscription
info = client.billing.get_current_plan(jwt)
print(info.plan, info.screenshots_used, "/", info.screenshots_limit)
# 30-day usage history
usage = client.billing.get_usage_history(jwt)
for day in usage:
print(day.date, day.screenshots)
# Upgrade plan
upgrade = client.billing.upgrade_plan(jwt, plan="GROWTH")
print("Checkout URL:", upgrade.payment_link)
# Verify payment after redirect back
result = client.billing.verify_payment(jwt, tx_ref=upgrade.tx_ref)
print("Payment status:", result.status)
# Cancel subscription
client.billing.cancel(jwt)
Workspaces
Workspace endpoints require a management JWT.
# Create a workspace
ws = client.workspaces.create(jwt, name="Acme Screenshots")
print("Workspace ID:", ws.id)
# List workspaces
for ws in client.workspaces.list(jwt):
print(ws.id, ws.name)
# Get workspace details
ws = client.workspaces.get(jwt, workspace_id="ws_abc")
for member in ws.members:
print(member.email, member.role)
# Invite a member
client.workspaces.invite(jwt, ws.id, email="colleague@acme.com", role="member")
# Change member role
client.workspaces.update_member_role(jwt, ws.id, user_id="user_xyz", role="admin")
# Remove a member
client.workspaces.remove_member(jwt, ws.id, user_id="user_xyz")
App Monitors
Monitor endpoints require a management JWT.
# Create a monitor (checks Instagram daily at 9am)
monitor = client.monitors.create(jwt, {
"app_name": "Instagram",
"platform": "both",
"schedule": "0 9 * * *",
"webhook_url": "https://your-app.com/monitor-webhook",
"notify_on_update": True,
})
print("Monitor ID:", monitor.id)
# List monitors
for m in client.monitors.list(jwt):
print(m.id, m.app_name, m.status)
# Get monitor detail
m = client.monitors.get(jwt, monitor_id="mon_abc")
# Get run history
runs = client.monitors.get_history(jwt, monitor_id="mon_abc", limit=10)
for run in runs:
print(run.run_at, run.changed, run.version_detected)
# Delete monitor
client.monitors.delete(jwt, monitor_id="mon_abc")
Zapier Integration
# Register a Zapier hook
sub = client.integrations.zapier_subscribe(
event="screenshot.completed",
target_url="https://hooks.zapier.com/hooks/catch/...",
)
print("Subscription ID:", sub.id)
# Unregister
client.integrations.zapier_unsubscribe(sub.id)
# Get a sample payload (for Zapier trigger setup)
samples = client.integrations.zapier_trigger_sample("screenshot.completed")
print(samples[0])
Configuration
| Parameter | Default | Description |
|---|---|---|
api_key |
required | Your sfa_... API key |
base_url |
https://api.screenshotfreeapi.com |
Override for self-hosted or testing |
timeout |
30 |
Socket timeout in seconds |
max_retries |
3 |
Retry attempts on 429 / 5xx before raising |
client = ScreenshotFreeAPIClient(
api_key="sfa_...",
base_url="https://api.screenshotfreeapi.com", # or "http://localhost:3000" for dev
timeout=60, # longer timeout for complex pages
max_retries=5, # extra retries for flaky connections
)
Health Check
health = client.health()
print(health.status) # "ok" | "degraded" | "down"
print(health.version) # API server version
print(health.uptime) # seconds since last restart
Support
- Documentation: https://api.screenshotfreeapi.com/docs
- Issues: https://github.com/chinkauchenna2021/screenshotfreeapi-python/issues
- Email: support@screenshotfreeapi.com
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file screenshotfreeapi-1.0.0.tar.gz.
File metadata
- Download URL: screenshotfreeapi-1.0.0.tar.gz
- Upload date:
- Size: 35.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e3e3cd6f5d24ad2d515c886050508a4597b686317e90c4050107727d28dadf6
|
|
| MD5 |
1747ab9cbf18f89038b55832f17600cc
|
|
| BLAKE2b-256 |
b95bcce26a6a1b4ad38d7ae6d1c814eda63fc27a9512395127f8e35ed1fb8c4b
|
File details
Details for the file screenshotfreeapi-1.0.0-py3-none-any.whl.
File metadata
- Download URL: screenshotfreeapi-1.0.0-py3-none-any.whl
- Upload date:
- Size: 31.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d2f89bf1f9bd4aa56e729b607d90f5f26e0ae3af4ba06a1d736addbadff1993
|
|
| MD5 |
c1ed1e45e03130c42bdbc1915209893a
|
|
| BLAKE2b-256 |
e3ea0f5c2dc1ffa604f95568e1e710b1997dd2c1858b8da61030aaaf3e2c2dc0
|