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.

POST/api/v1/screenshot

Authentication

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

ParameterTypeDefaultDescription
urlrequiredstringAbsolute URL of the page to capture. Public http(s) addresses only — localhost, private hosts, and internal IP ranges are rejected.
full_pagebooleanfalseCapture the full scrollable height of the page instead of just the viewport. width and height still set the viewport the page lays out in.
formatstring"png"Output image encoding. One of: png, jpeg, webp.
widthinteger1440Viewport width in CSS pixels. Range: 320–3,840.
heightinteger900Viewport height in CSS pixels. Range: 320–2,160.
responsestring"image""image" streams the encoded image bytes back; "json" returns capture metadata and a hosted image URL instead. One of: image, json.

Examples

cURL
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.png
Node.js
const 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 URL
Python
import 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 bytes

Response

Success status: 200. The rendered image bytes, or capture metadata in JSON mode. Content types: image/png, image/jpeg, image/webp, application/json.

200 OK — "response": "json"
{
  "id": "s3Nv8qLp5TkX2wYc",
  "url": "https://shotwisp.com/s/s3Nv8qLp5TkX2wYc",
  "format": "png",
  "width": 1280,
  "height": 800,
  "full_page": false,
  "duration_ms": 2140,
  "size_bytes": 208431
}
Every response carries rate-limit headers; metered successes add quota headers. Byte responses (images, PDFs) carry the capture id in x-shotwisp-id.

Errors

Every error uses the envelope {"error": {"code", "message"}}. This endpoint can return:

CodeHTTP statusRetryableMeaning
unauthorized401noThe 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_exceeded402noThe 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_error422noA 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_limited429yesThe 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_limited429yesThe 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.
internal500yesUnexpected error on Shotwisp's side. The request was not metered. Safe to retry with exponential backoff.
capture_failed502yesThe 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.