Screenshot API
Take a screenshot
Loads the URL in a managed browser and returns the rendered image — by default the encoded bytes (Content-Type image/png, image/jpeg, or image/webp per `format`), or capture metadata plus a hosted image URL when `"response": "json"`. The page is navigated with JavaScript enabled, waiting for the load event plus up to 3s of network settle (navigation budget 25s, total render budget 60s). Each successful capture consumes one render unit of the monthly quota; failed captures are not metered.
/api/v1/screenshotAuthentication
Send your API key as a bearer token on every request: Authorization: Bearer sw_.... Create keys in the dashboard; see the authentication docs for details.
Parameters
Request content type: application/json
| Parameter | Type | Default | Description |
|---|---|---|---|
urlrequired | string | — | Absolute URL of the page to capture. Public http(s) addresses only — localhost, private hosts, and internal IP ranges are rejected. |
full_page | boolean | false | Capture the full scrollable height of the page instead of just the viewport. width and height still set the viewport the page lays out in. |
format | string | "png" | Output image encoding. One of: png, jpeg, webp. |
width | integer | 1440 | Viewport width in CSS pixels. Range: 320–3,840. |
height | integer | 900 | Viewport height in CSS pixels. Range: 320–2,160. |
response | string | "image" | "image" streams the encoded image bytes back; "json" returns capture metadata and a hosted image URL instead. One of: image, json. |
Examples
curl -X POST https://shotwisp.com/api/v1/screenshot \
-H "Authorization: Bearer $SHOTWISP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "full_page": true, "format": "png"}' \
-o shot.pngconst res = await fetch("https://shotwisp.com/api/v1/screenshot", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SHOTWISP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ url: "https://example.com", response: "json" }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
const shot = await res.json();
console.log(shot.url); // hosted image URLimport os, requests
res = requests.post(
"https://shotwisp.com/api/v1/screenshot",
headers={"Authorization": f"Bearer {os.environ['SHOTWISP_API_KEY']}"},
json={"url": "https://example.com", "width": 1280, "height": 800},
timeout=90,
)
res.raise_for_status()
open("shot.png", "wb").write(res.content) # default response is image bytesResponse
Success status: 200. The rendered image bytes, or capture metadata in JSON mode. Content types: image/png, image/jpeg, image/webp, application/json.
{
"id": "s3Nv8qLp5TkX2wYc",
"url": "https://shotwisp.com/s/s3Nv8qLp5TkX2wYc",
"format": "png",
"width": 1280,
"height": 800,
"full_page": false,
"duration_ms": 2140,
"size_bytes": 208431
}x-shotwisp-id.Errors
Every error uses the envelope {"error": {"code", "message"}}. This endpoint can return:
| Code | HTTP status | Retryable | Meaning |
|---|---|---|---|
unauthorized | 401 | no | The API key is missing, malformed, revoked, or the account is suspended or unverified. Do not retry with the same key. Fix the Authorization header or create a new API key. |
quota_exceeded | 402 | no | The monthly upload or render quota is used up and overages are off or unavailable on the plan. Do not retry until the monthly quota resets, the plan is upgraded, or overages are enabled. |
validation_error | 422 | no | A request field failed validation. The message states which field and why. Do not retry unchanged. The message names the failing field; fix it first. |
rate_limited | 429 | yes | The plan's per-minute request budget is spent. Rejected requests are not metered. Retry after waiting the number of seconds in the Retry-After header, then back off exponentially with jitter. |
concurrency_limited | 429 | yes | The plan's concurrent-capture cap is fully in use by renders still in progress. Retry after the Retry-After delay once an in-flight capture finishes. Reduce parallelism to the plan's concurrency cap. |
internal | 500 | yes | Unexpected error on Shotwisp's side. The request was not metered. Safe to retry with exponential backoff. |
capture_failed | 502 | yes | The target page could not be loaded or captured (unreachable, timed out, or blocked the renderer). Failed captures are not metered. Retry once or twice with backoff. If it persists, the target page cannot be captured — do not keep retrying. |
Limits and machine-readable spec
Plan quotas, rate limits, and every fixed limit are consolidated in Limits. This endpoint is also fully described in the OpenAPI 3.1 specification (operationId createScreenshot). Building with an AI agent? Read the AI agent guide.