site-shot 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .mypy_cache/
9
+ .ruff_cache/
10
+ .pytest_cache/
11
+ .coverage
12
+ htmlcov/
13
+ *.png
14
+ *.jpeg
15
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Site-Shot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,294 @@
1
+ Metadata-Version: 2.5
2
+ Name: site-shot
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for the Site-Shot screenshot API — capture website screenshots in a real Chromium browser (full-page, country proxies, ad & cookie-banner removal). Zero dependencies.
5
+ Project-URL: Homepage, https://www.site-shot.com/
6
+ Project-URL: Documentation, https://www.site-shot.com/#documentation
7
+ Project-URL: API keys & pricing, https://www.site-shot.com/pricing/
8
+ Project-URL: For AI agents, https://www.site-shot.com/ai-agents/
9
+ Project-URL: Repository, https://github.com/site-shot/site-shot-python
10
+ Project-URL: Issues, https://github.com/site-shot/site-shot-python/issues
11
+ Author-email: Site-Shot <support@site-shot.com>
12
+ Maintainer-email: Site-Shot <support@site-shot.com>
13
+ License: MIT
14
+ License-File: LICENSE
15
+ Keywords: api-client,full-page-screenshot,screenshot,screenshot-api,sdk,site-shot,url-to-image,web-capture,web-scraping,website-screenshot
16
+ Classifier: Development Status :: 4 - Beta
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3 :: Only
22
+ Classifier: Programming Language :: Python :: 3.9
23
+ Classifier: Programming Language :: Python :: 3.10
24
+ Classifier: Programming Language :: Python :: 3.11
25
+ Classifier: Programming Language :: Python :: 3.12
26
+ Classifier: Programming Language :: Python :: 3.13
27
+ Classifier: Topic :: Internet :: WWW/HTTP
28
+ Classifier: Topic :: Multimedia :: Graphics :: Capture :: Screen Capture
29
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
30
+ Classifier: Typing :: Typed
31
+ Requires-Python: >=3.9
32
+ Description-Content-Type: text/markdown
33
+
34
+ # site-shot
35
+
36
+ Official Python SDK for the [Site-Shot](https://www.site-shot.com/) screenshot API.
37
+
38
+ ```bash
39
+ pip install site-shot
40
+ ```
41
+
42
+ Capture website screenshots in a real Chromium browser: full-page capture, country
43
+ proxies, automatic ad & cookie-banner removal. **Zero dependencies** (standard
44
+ library only), Python >= 3.9, fully typed (`py.typed`), sync.
45
+
46
+ API plans from **$5/mo** (X-Lite, 2,000 screenshots). The
47
+ [free browser tool](https://www.site-shot.com/) is no-signup, but the **API
48
+ requires a key** — get one at <https://www.site-shot.com/pricing/>.
49
+
50
+ ## Quickstart
51
+
52
+ ```python
53
+ from site_shot import SiteShot
54
+
55
+ client = SiteShot("YOUR_API_KEY") # or set SITESHOT_API_KEY in the env
56
+
57
+ png = client.capture("example.com", full_size=True) # png is bytes
58
+ with open("shot.png", "wb") as f:
59
+ f.write(png)
60
+ ```
61
+
62
+ The import package is `site_shot`; the pip name is `site-shot` (PyPI treats the
63
+ two spellings as the same project).
64
+
65
+ Building an AI agent with an MCP-capable client instead? Use the sibling MCP
66
+ server: `npx -y site-shot-mcp`. Working in Node? `npm install site-shot-sdk`.
67
+
68
+ ## Capture cleanly (ads and cookie banners removed)
69
+
70
+ ```python
71
+ png = client.capture(
72
+ "https://example.com/",
73
+ no_ads=True,
74
+ no_cookie_popup=True,
75
+ )
76
+ ```
77
+
78
+ ## Return modes
79
+
80
+ One capture concept, four return modes — pick the method, not a flag:
81
+
82
+ ```python
83
+ # 1. Bytes (primary mode)
84
+ png = client.capture("https://example.com/")
85
+
86
+ # 2. Straight to a file
87
+ client.capture_to_file("https://example.com/", "shot.png")
88
+
89
+ # 3. Base64 string (data URLs, LLM vision payloads)
90
+ b64 = client.capture_base64("https://example.com/")
91
+
92
+ # 4. Full JSON result (image plus metadata; add source_code for rendered HTML)
93
+ meta = client.capture_json("https://example.com/", source_code=True)
94
+ print(meta["source_code"][:200])
95
+ ```
96
+
97
+ `url` is positional-only, every capture option is a keyword argument. An options
98
+ dict works too: `client.capture(url, **options)`.
99
+
100
+ ### `build_url()` — build the request URL without executing it
101
+
102
+ ```python
103
+ url = client.build_url("https://example.com/", width=1280)
104
+ ```
105
+
106
+ > **⚠️ Key-leak warning:** the returned URL **embeds your API key** (`userkey`),
107
+ > and Site-Shot has no signed-URL scheme. Use it for debugging or server-side
108
+ > proxying only. **Never** put it in an `<img src>` or anywhere a browser or
109
+ > third party can see it.
110
+
111
+ ## Screenshots from another country
112
+
113
+ Pass `country` as an **ISO 3166-1 alpha-2 code** (e.g. `"DE"`, `"BR"`, `"JP"` —
114
+ the current list is at <https://www.site-shot.com/countries/>). It automatically
115
+ sets a matching IP, language, time zone, and geolocation. Full country names are
116
+ not valid values.
117
+
118
+ By default, if the requested country has no capacity at that moment, the API
119
+ silently falls back to a US vantage point. Set `strict_country` to fail fast
120
+ instead:
121
+
122
+ ```python
123
+ from site_shot import SiteShot, CountryUnavailableError
124
+
125
+ client = SiteShot("YOUR_API_KEY", retries=2) # recommended for geo captures
126
+
127
+ try:
128
+ shot = client.capture(
129
+ "https://whatismycountry.com/",
130
+ country="DE", # ISO 3166-1 alpha-2, never "Germany"
131
+ strict_country=True,
132
+ no_ads=True,
133
+ no_cookie_popup=True,
134
+ )
135
+ except CountryUnavailableError:
136
+ ... # No live DE capacity right now — retry later or drop strict_country.
137
+ ```
138
+
139
+ A GPS override, independent of the exit IP:
140
+
141
+ ```python
142
+ png = client.capture(
143
+ "https://whatismycountry.com/",
144
+ country="JP", # ISO code
145
+ geolocation="35.6762,139.6503",
146
+ time_zone="Asia/Tokyo",
147
+ language="ja",
148
+ )
149
+ ```
150
+
151
+ More on geotargeted screenshots:
152
+ <https://www.site-shot.com/blog/screenshot-website-from-another-country/>
153
+
154
+ ## Options
155
+
156
+ Option names mirror the HTTP query parameters **verbatim** (snake_case) — the
157
+ [API reference on the homepage](https://www.site-shot.com/#documentation) and
158
+ this SDK share one vocabulary. Booleans are accepted and coerced to `1`/`0`.
159
+ **Unknown options pass through verbatim**, so future API params work without an
160
+ SDK update.
161
+
162
+ | Option | Type / range | API default | Notes |
163
+ |---|---|---|---|
164
+ | `url` | str, **required** (positional-only) | — | bare domains like `example.com` accepted (`https://` assumed) |
165
+ | `width` | int 100–8000 | 1024 | viewport width |
166
+ | `height` | int 100–20000 | 768 | viewport height |
167
+ | `zoom` | int 5–1000 | 100 | percentage zoom |
168
+ | `full_size` | bool | False | full-page capture (height capped by `max_height`) |
169
+ | `max_height` | int 100–20000 | 15000 | only meaningful with `full_size` |
170
+ | `scaled_width` | int 50–10000 | — | scale result image to width |
171
+ | `format` | `"png"` \| `"jpeg"` | png | |
172
+ | `delay_time` | int ms 0–60000 | 1500 | wait before capture (SPAs, animations) |
173
+ | `timeout` | int ms 0–120000 | 60000 | server-side render deadline |
174
+ | `user_agent` | str | — | custom UA for the rendering browser |
175
+ | `request_headers` | dict | — | emitted as repeated `request_header=Name:value` params |
176
+ | `http_proxy` / `proxy_username` / `proxy_password` | str | — | bring-your-own-proxy passthrough |
177
+ | `proxy_rotation` | bool | True | |
178
+ | `source_code` | bool | False | include rendered HTML (use with `capture_json`) |
179
+ | `javascript_code` | str | — | inject JS into the page before capture |
180
+ | `no_ads` | bool | False | remove ads |
181
+ | `no_cookie_popup` | bool | False | remove cookie-consent banners |
182
+ | `country` | ISO 3166-1 alpha-2 | — | e.g. `"DE"` — auto-sets language/time zone/geolocation |
183
+ | `strict_country` | bool | False | fail fast instead of silent US fallback |
184
+ | `geolocation` | `"lat,lng"` | — | GPS override, independent of IP |
185
+ | `language` | str | from `country` / en | Accept-Language |
186
+ | `time_zone` | IANA name | America/New_York | see <https://www.site-shot.com/time-zones/> |
187
+
188
+ `userkey` (your API key) is owned by the `SiteShot` constructor — never a
189
+ per-call option. `response_type` is owned by the return-mode methods.
190
+
191
+ A `CaptureOptions` `TypedDict` is exported for callers who build option dicts:
192
+
193
+ ```python
194
+ from site_shot import CaptureOptions
195
+
196
+ options: CaptureOptions = {"width": 1280, "full_size": True, "country": "DE"}
197
+ png = client.capture("https://example.com/", **options)
198
+ ```
199
+
200
+ `url` is positional-only on every capture method, so an options dict that also
201
+ carries a `url` key still type-checks and runs — the positional URL wins, just
202
+ like `userkey`. (These snippets are type-checked under `mypy --strict` in the
203
+ test suite.)
204
+
205
+ The SDK sends GET requests; very long `javascript_code` or `user_agent` values
206
+ can exceed practical URL length limits (~8 KB).
207
+
208
+ ## Client options
209
+
210
+ ```python
211
+ client = SiteShot(
212
+ "YOUR_API_KEY",
213
+ base_url="https://api.site-shot.com/",
214
+ timeout=90, # client-side deadline in SECONDS; default = server `timeout` + 30s headroom
215
+ retries=0, # connection-level retries only (see below)
216
+ )
217
+ ```
218
+
219
+ The API key falls back to the `SITESHOT_API_KEY` environment variable — the same
220
+ variable the [Site-Shot MCP server](https://www.npmjs.com/package/site-shot-mcp)
221
+ uses:
222
+
223
+ ```python
224
+ import os
225
+ client = SiteShot() # reads SITESHOT_API_KEY
226
+ client = SiteShot(os.environ["SITESHOT_API_KEY"]) # or pass it explicitly
227
+ ```
228
+
229
+ Two different things are called "timeout", exactly as in the HTTP API:
230
+
231
+ - the constructor's `timeout` is the **client-side deadline in seconds**, and it
232
+ bounds the *whole* exchange — connecting, waiting for the status line and
233
+ headers, and downloading the image body (a slow drip in any of those phases
234
+ cannot outlast it);
235
+ - the capture option `timeout` is the **server-side render deadline in
236
+ milliseconds** (the verbatim HTTP param name).
237
+
238
+ `SiteShot` also works as a context manager (`with SiteShot(...) as client:`); it
239
+ holds no pooled resources, so closing it is a no-op.
240
+
241
+ ## Errors
242
+
243
+ Every error is a subclass of `SiteShotError` and carries `http_status` and the
244
+ raw response `body` where available.
245
+
246
+ | Error | Raised when |
247
+ |---|---|
248
+ | `AuthError` | missing or rejected API key (also raised by the constructor on an empty key) |
249
+ | `QuotaError` | plan quota exhausted / payment required |
250
+ | `CountryUnavailableError` | `strict_country` capture and the requested country has no capacity right now |
251
+ | `InvalidParamsError` | the API rejected a parameter (out-of-range width, bad format, ...) |
252
+ | `SiteShotTimeoutError` | client-side deadline, or the API reported a render timeout |
253
+ | `APIError` | anything else (server errors, unparseable bodies, connection failures) |
254
+
255
+ ```python
256
+ from site_shot import SiteShot, SiteShotError, QuotaError
257
+
258
+ try:
259
+ png = client.capture("https://example.com/")
260
+ except QuotaError as e:
261
+ print("out of quota:", e, e.http_status)
262
+ except SiteShotError as e:
263
+ print("capture failed:", e)
264
+ ```
265
+
266
+ Under the hood the SDK always asks the API for a JSON response and decodes the
267
+ image itself — so errors surface as typed exceptions instead of an error picture
268
+ pretending to be your screenshot.
269
+
270
+ ## Retries
271
+
272
+ Default: `0`. Screenshots cost quota, and a timed-out render may still have
273
+ consumed one — so the SDK never auto-retries a render that completed with an
274
+ error. The `retries` option applies to **connection-level failures only** (DNS,
275
+ connection reset, no bytes received), with jittered backoff. `retries=2` is a
276
+ sensible setting for `country=` captures.
277
+
278
+ ## Requirements
279
+
280
+ - Python >= 3.9
281
+ - Zero dependencies (standard library `urllib` only)
282
+
283
+ ## Links
284
+
285
+ - Pricing & API keys: <https://www.site-shot.com/pricing/>
286
+ - For AI agents & assistants: <https://www.site-shot.com/ai-agents/>
287
+ - Supported countries: <https://www.site-shot.com/countries/>
288
+ - Time zones: <https://www.site-shot.com/time-zones/>
289
+ - Node.js SDK: `npm install site-shot-sdk`
290
+ - MCP server (agent tooling sibling): `npx -y site-shot-mcp`
291
+
292
+ ## License
293
+
294
+ MIT
@@ -0,0 +1,261 @@
1
+ # site-shot
2
+
3
+ Official Python SDK for the [Site-Shot](https://www.site-shot.com/) screenshot API.
4
+
5
+ ```bash
6
+ pip install site-shot
7
+ ```
8
+
9
+ Capture website screenshots in a real Chromium browser: full-page capture, country
10
+ proxies, automatic ad & cookie-banner removal. **Zero dependencies** (standard
11
+ library only), Python >= 3.9, fully typed (`py.typed`), sync.
12
+
13
+ API plans from **$5/mo** (X-Lite, 2,000 screenshots). The
14
+ [free browser tool](https://www.site-shot.com/) is no-signup, but the **API
15
+ requires a key** — get one at <https://www.site-shot.com/pricing/>.
16
+
17
+ ## Quickstart
18
+
19
+ ```python
20
+ from site_shot import SiteShot
21
+
22
+ client = SiteShot("YOUR_API_KEY") # or set SITESHOT_API_KEY in the env
23
+
24
+ png = client.capture("example.com", full_size=True) # png is bytes
25
+ with open("shot.png", "wb") as f:
26
+ f.write(png)
27
+ ```
28
+
29
+ The import package is `site_shot`; the pip name is `site-shot` (PyPI treats the
30
+ two spellings as the same project).
31
+
32
+ Building an AI agent with an MCP-capable client instead? Use the sibling MCP
33
+ server: `npx -y site-shot-mcp`. Working in Node? `npm install site-shot-sdk`.
34
+
35
+ ## Capture cleanly (ads and cookie banners removed)
36
+
37
+ ```python
38
+ png = client.capture(
39
+ "https://example.com/",
40
+ no_ads=True,
41
+ no_cookie_popup=True,
42
+ )
43
+ ```
44
+
45
+ ## Return modes
46
+
47
+ One capture concept, four return modes — pick the method, not a flag:
48
+
49
+ ```python
50
+ # 1. Bytes (primary mode)
51
+ png = client.capture("https://example.com/")
52
+
53
+ # 2. Straight to a file
54
+ client.capture_to_file("https://example.com/", "shot.png")
55
+
56
+ # 3. Base64 string (data URLs, LLM vision payloads)
57
+ b64 = client.capture_base64("https://example.com/")
58
+
59
+ # 4. Full JSON result (image plus metadata; add source_code for rendered HTML)
60
+ meta = client.capture_json("https://example.com/", source_code=True)
61
+ print(meta["source_code"][:200])
62
+ ```
63
+
64
+ `url` is positional-only, every capture option is a keyword argument. An options
65
+ dict works too: `client.capture(url, **options)`.
66
+
67
+ ### `build_url()` — build the request URL without executing it
68
+
69
+ ```python
70
+ url = client.build_url("https://example.com/", width=1280)
71
+ ```
72
+
73
+ > **⚠️ Key-leak warning:** the returned URL **embeds your API key** (`userkey`),
74
+ > and Site-Shot has no signed-URL scheme. Use it for debugging or server-side
75
+ > proxying only. **Never** put it in an `<img src>` or anywhere a browser or
76
+ > third party can see it.
77
+
78
+ ## Screenshots from another country
79
+
80
+ Pass `country` as an **ISO 3166-1 alpha-2 code** (e.g. `"DE"`, `"BR"`, `"JP"` —
81
+ the current list is at <https://www.site-shot.com/countries/>). It automatically
82
+ sets a matching IP, language, time zone, and geolocation. Full country names are
83
+ not valid values.
84
+
85
+ By default, if the requested country has no capacity at that moment, the API
86
+ silently falls back to a US vantage point. Set `strict_country` to fail fast
87
+ instead:
88
+
89
+ ```python
90
+ from site_shot import SiteShot, CountryUnavailableError
91
+
92
+ client = SiteShot("YOUR_API_KEY", retries=2) # recommended for geo captures
93
+
94
+ try:
95
+ shot = client.capture(
96
+ "https://whatismycountry.com/",
97
+ country="DE", # ISO 3166-1 alpha-2, never "Germany"
98
+ strict_country=True,
99
+ no_ads=True,
100
+ no_cookie_popup=True,
101
+ )
102
+ except CountryUnavailableError:
103
+ ... # No live DE capacity right now — retry later or drop strict_country.
104
+ ```
105
+
106
+ A GPS override, independent of the exit IP:
107
+
108
+ ```python
109
+ png = client.capture(
110
+ "https://whatismycountry.com/",
111
+ country="JP", # ISO code
112
+ geolocation="35.6762,139.6503",
113
+ time_zone="Asia/Tokyo",
114
+ language="ja",
115
+ )
116
+ ```
117
+
118
+ More on geotargeted screenshots:
119
+ <https://www.site-shot.com/blog/screenshot-website-from-another-country/>
120
+
121
+ ## Options
122
+
123
+ Option names mirror the HTTP query parameters **verbatim** (snake_case) — the
124
+ [API reference on the homepage](https://www.site-shot.com/#documentation) and
125
+ this SDK share one vocabulary. Booleans are accepted and coerced to `1`/`0`.
126
+ **Unknown options pass through verbatim**, so future API params work without an
127
+ SDK update.
128
+
129
+ | Option | Type / range | API default | Notes |
130
+ |---|---|---|---|
131
+ | `url` | str, **required** (positional-only) | — | bare domains like `example.com` accepted (`https://` assumed) |
132
+ | `width` | int 100–8000 | 1024 | viewport width |
133
+ | `height` | int 100–20000 | 768 | viewport height |
134
+ | `zoom` | int 5–1000 | 100 | percentage zoom |
135
+ | `full_size` | bool | False | full-page capture (height capped by `max_height`) |
136
+ | `max_height` | int 100–20000 | 15000 | only meaningful with `full_size` |
137
+ | `scaled_width` | int 50–10000 | — | scale result image to width |
138
+ | `format` | `"png"` \| `"jpeg"` | png | |
139
+ | `delay_time` | int ms 0–60000 | 1500 | wait before capture (SPAs, animations) |
140
+ | `timeout` | int ms 0–120000 | 60000 | server-side render deadline |
141
+ | `user_agent` | str | — | custom UA for the rendering browser |
142
+ | `request_headers` | dict | — | emitted as repeated `request_header=Name:value` params |
143
+ | `http_proxy` / `proxy_username` / `proxy_password` | str | — | bring-your-own-proxy passthrough |
144
+ | `proxy_rotation` | bool | True | |
145
+ | `source_code` | bool | False | include rendered HTML (use with `capture_json`) |
146
+ | `javascript_code` | str | — | inject JS into the page before capture |
147
+ | `no_ads` | bool | False | remove ads |
148
+ | `no_cookie_popup` | bool | False | remove cookie-consent banners |
149
+ | `country` | ISO 3166-1 alpha-2 | — | e.g. `"DE"` — auto-sets language/time zone/geolocation |
150
+ | `strict_country` | bool | False | fail fast instead of silent US fallback |
151
+ | `geolocation` | `"lat,lng"` | — | GPS override, independent of IP |
152
+ | `language` | str | from `country` / en | Accept-Language |
153
+ | `time_zone` | IANA name | America/New_York | see <https://www.site-shot.com/time-zones/> |
154
+
155
+ `userkey` (your API key) is owned by the `SiteShot` constructor — never a
156
+ per-call option. `response_type` is owned by the return-mode methods.
157
+
158
+ A `CaptureOptions` `TypedDict` is exported for callers who build option dicts:
159
+
160
+ ```python
161
+ from site_shot import CaptureOptions
162
+
163
+ options: CaptureOptions = {"width": 1280, "full_size": True, "country": "DE"}
164
+ png = client.capture("https://example.com/", **options)
165
+ ```
166
+
167
+ `url` is positional-only on every capture method, so an options dict that also
168
+ carries a `url` key still type-checks and runs — the positional URL wins, just
169
+ like `userkey`. (These snippets are type-checked under `mypy --strict` in the
170
+ test suite.)
171
+
172
+ The SDK sends GET requests; very long `javascript_code` or `user_agent` values
173
+ can exceed practical URL length limits (~8 KB).
174
+
175
+ ## Client options
176
+
177
+ ```python
178
+ client = SiteShot(
179
+ "YOUR_API_KEY",
180
+ base_url="https://api.site-shot.com/",
181
+ timeout=90, # client-side deadline in SECONDS; default = server `timeout` + 30s headroom
182
+ retries=0, # connection-level retries only (see below)
183
+ )
184
+ ```
185
+
186
+ The API key falls back to the `SITESHOT_API_KEY` environment variable — the same
187
+ variable the [Site-Shot MCP server](https://www.npmjs.com/package/site-shot-mcp)
188
+ uses:
189
+
190
+ ```python
191
+ import os
192
+ client = SiteShot() # reads SITESHOT_API_KEY
193
+ client = SiteShot(os.environ["SITESHOT_API_KEY"]) # or pass it explicitly
194
+ ```
195
+
196
+ Two different things are called "timeout", exactly as in the HTTP API:
197
+
198
+ - the constructor's `timeout` is the **client-side deadline in seconds**, and it
199
+ bounds the *whole* exchange — connecting, waiting for the status line and
200
+ headers, and downloading the image body (a slow drip in any of those phases
201
+ cannot outlast it);
202
+ - the capture option `timeout` is the **server-side render deadline in
203
+ milliseconds** (the verbatim HTTP param name).
204
+
205
+ `SiteShot` also works as a context manager (`with SiteShot(...) as client:`); it
206
+ holds no pooled resources, so closing it is a no-op.
207
+
208
+ ## Errors
209
+
210
+ Every error is a subclass of `SiteShotError` and carries `http_status` and the
211
+ raw response `body` where available.
212
+
213
+ | Error | Raised when |
214
+ |---|---|
215
+ | `AuthError` | missing or rejected API key (also raised by the constructor on an empty key) |
216
+ | `QuotaError` | plan quota exhausted / payment required |
217
+ | `CountryUnavailableError` | `strict_country` capture and the requested country has no capacity right now |
218
+ | `InvalidParamsError` | the API rejected a parameter (out-of-range width, bad format, ...) |
219
+ | `SiteShotTimeoutError` | client-side deadline, or the API reported a render timeout |
220
+ | `APIError` | anything else (server errors, unparseable bodies, connection failures) |
221
+
222
+ ```python
223
+ from site_shot import SiteShot, SiteShotError, QuotaError
224
+
225
+ try:
226
+ png = client.capture("https://example.com/")
227
+ except QuotaError as e:
228
+ print("out of quota:", e, e.http_status)
229
+ except SiteShotError as e:
230
+ print("capture failed:", e)
231
+ ```
232
+
233
+ Under the hood the SDK always asks the API for a JSON response and decodes the
234
+ image itself — so errors surface as typed exceptions instead of an error picture
235
+ pretending to be your screenshot.
236
+
237
+ ## Retries
238
+
239
+ Default: `0`. Screenshots cost quota, and a timed-out render may still have
240
+ consumed one — so the SDK never auto-retries a render that completed with an
241
+ error. The `retries` option applies to **connection-level failures only** (DNS,
242
+ connection reset, no bytes received), with jittered backoff. `retries=2` is a
243
+ sensible setting for `country=` captures.
244
+
245
+ ## Requirements
246
+
247
+ - Python >= 3.9
248
+ - Zero dependencies (standard library `urllib` only)
249
+
250
+ ## Links
251
+
252
+ - Pricing & API keys: <https://www.site-shot.com/pricing/>
253
+ - For AI agents & assistants: <https://www.site-shot.com/ai-agents/>
254
+ - Supported countries: <https://www.site-shot.com/countries/>
255
+ - Time zones: <https://www.site-shot.com/time-zones/>
256
+ - Node.js SDK: `npm install site-shot-sdk`
257
+ - MCP server (agent tooling sibling): `npx -y site-shot-mcp`
258
+
259
+ ## License
260
+
261
+ MIT
@@ -0,0 +1,83 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "site-shot"
7
+ version = "0.1.0"
8
+ description = "Official Python SDK for the Site-Shot screenshot API — capture website screenshots in a real Chromium browser (full-page, country proxies, ad & cookie-banner removal). Zero dependencies."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Site-Shot", email = "support@site-shot.com" }]
13
+ maintainers = [{ name = "Site-Shot", email = "support@site-shot.com" }]
14
+ keywords = [
15
+ "screenshot",
16
+ "screenshot-api",
17
+ "website-screenshot",
18
+ "site-shot",
19
+ "sdk",
20
+ "api-client",
21
+ "full-page-screenshot",
22
+ "url-to-image",
23
+ "web-capture",
24
+ "web-scraping",
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Intended Audience :: Developers",
29
+ "License :: OSI Approved :: MIT License",
30
+ "Operating System :: OS Independent",
31
+ "Programming Language :: Python :: 3",
32
+ "Programming Language :: Python :: 3 :: Only",
33
+ "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
35
+ "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
38
+ "Topic :: Internet :: WWW/HTTP",
39
+ "Topic :: Multimedia :: Graphics :: Capture :: Screen Capture",
40
+ "Topic :: Software Development :: Libraries :: Python Modules",
41
+ "Typing :: Typed",
42
+ ]
43
+ dependencies = []
44
+
45
+ [project.urls]
46
+ Homepage = "https://www.site-shot.com/"
47
+ Documentation = "https://www.site-shot.com/#documentation"
48
+ "API keys & pricing" = "https://www.site-shot.com/pricing/"
49
+ "For AI agents" = "https://www.site-shot.com/ai-agents/"
50
+ Repository = "https://github.com/site-shot/site-shot-python"
51
+ Issues = "https://github.com/site-shot/site-shot-python/issues"
52
+
53
+ [tool.hatch.build.targets.wheel]
54
+ packages = ["src/site_shot"]
55
+
56
+ [tool.hatch.build.targets.sdist]
57
+ include = [
58
+ "/src",
59
+ "/tests",
60
+ "/scripts",
61
+ "/README.md",
62
+ "/LICENSE",
63
+ ]
64
+
65
+ [tool.mypy]
66
+ strict = true
67
+ # Always check the checked-out source, never a stale installed copy.
68
+ mypy_path = "src"
69
+ # The tests are checked too: consumer-shaped usage is exactly where the exported
70
+ # TypedDict broke, and checking only src/ is why that shipped. Bodies are
71
+ # checked everywhere; only the legacy test module is excused from annotating
72
+ # every def.
73
+ files = ["src/site_shot", "tests"]
74
+
75
+ [[tool.mypy.overrides]]
76
+ module = ["test_client", "tests.test_client"]
77
+ disallow_untyped_defs = false
78
+ disallow_incomplete_defs = false
79
+ disallow_untyped_calls = false
80
+
81
+ [tool.ruff]
82
+ line-length = 100
83
+ target-version = "py39"