Metadata-Version: 2.4
Name: screenshotfreeapi
Version: 1.0.0
Summary: Official Python SDK for ScreenshotFreeAPI — screenshot-as-a-service
Author-email: ScreenshotFreeAPI <support@screenshotfreeapi.com>
License: MIT
Project-URL: Homepage, https://api.screenshotfreeapi.com
Project-URL: Repository, https://github.com/chinkauchenna2021/screenshotfreeapi-python
Project-URL: Issues, https://github.com/chinkauchenna2021/screenshotfreeapi-python/issues
Keywords: screenshot,api,headless,web-capture,pdf
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# screenshotfreeapi Python SDK

Official Python SDK for [ScreenshotFreeAPI](https://api.screenshotfreeapi.com) — 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

```bash
pip install screenshotfreeapi
```

---

## Quick Start

```python
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

```python
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.

```python
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

```python
result = client.capture(
    "https://github.com",
    element=".hero-section",
)
```

### Full Page

```python
result = client.capture("https://example.com", full_page=True)
```

### PDF Generation

```python
result = client.capture(
    "https://example.com",
    format="pdf",
    paper_size="A4",
)
print(result.screenshots[0].url)  # PDF download URL
```

### All Options

```python
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.

```python
# 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)
```

```python
# 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.

```python
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

```python
# 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

```python
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:

```python
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.

```python
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:

```python
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

```python
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)

```python
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

```python
new_token = client.auth.refresh_token(token_result.refresh_token)
```

---

## Billing

Billing endpoints require a management JWT (see above).

```python
# 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.

```python
# 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.

```python
# 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

```python
# 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 |

```python
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

```python
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](https://api.screenshotfreeapi.com/docs)
- Issues: [https://github.com/chinkauchenna2021/screenshotfreeapi-python/issues](https://github.com/chinkauchenna2021/screenshotfreeapi-python/issues)
- Email: [support@screenshotfreeapi.com](mailto:support@screenshotfreeapi.com)

---

## License

MIT
