ShotAPI

Reference

API docs

Base URL: https://shot-api.com. Send a public URL or HTML to POST /api/v1/render and get PNG, JPEG, or PDF back. ShotAPI Pro is $19/month (10,000 renders; demo is 100/month).

Auth

Customer routes accept either header. The value is the raw secret, with no extra prefix on X-API-Key:

A missing key returns 401 unauthorized. The server stores a SHA-256 hash and shows a prefix (the first 16 characters) on later reads. The sample secret sk_demo_local_dev_only works only while demo mode is on. On Vercel, demo mode is off unless SHOTAPI_DEMO=1. Mint a real key with the admin token below.

POST /api/v1/keys uses SHOTAPI_ADMIN_TOKEN in those same headers. That token is not a customer API key. Browser callers may POST cross-origin; CORS allows Authorization, Content-Type, and X-API-Key.

Failures are JSON: { "error": { "code", "message", "requestId" } }, with x-request-id on the response.

POST /api/v1/render

One route covers both. format: "png" or "jpeg" returns a screenshot (image/png or image/jpeg). format: "pdf" returns application/pdf. Send exactly one of url or html. jpg is accepted and stored as JPEG.

FieldDefaultNotes
urlPublic http(s) URL, max 2048 characters. Credentials, localhost, and private networks are rejected.
htmlDocument loaded with setContent. Default max 500 KB (SHOTAPI_MAX_HTML_BYTES).
formatpngpng, jpeg, jpg, or pdf.
viewport.width1280320–1920.
viewport.height800240–4000.
viewport.deviceScaleFactor11–2.
fullPagefalsePNG and JPEG only. PDF uses the paper size below.
quality80JPEG quality, 1–100. Ignored for PNG and PDF.
waitUntilloadload, domcontentloaded, or networkidle.
delayMs0Extra wait after load, max 5000.
pdf.formatA4A4, Letter, or Legal. PDF is rendered with print media.
pdf.landscapefalse
pdf.printBackgroundtrue

Success headers: content-type, content-disposition (inline; filename="shot.png", shot.jpg, or shot.pdf), x-shotapi-usage, x-shotapi-limit, x-shotapi-plan, x-shotapi-storage, and x-request-id. A target page that returns 404 is still rendered. The API does not forward cookies or your API key. The browser user agent is ShotAPI/0.1. Request bodies must be JSON, up to 1 MB by default (SHOTAPI_MAX_BODY_BYTES).

HTML to PNG
curl -sS -X POST https://shot-api.com/api/v1/render \
  -H "authorization: Bearer sk_demo_local_dev_only" \
  -H "content-type: application/json" \
  -o shot.png \
  -d '{"html":"<h1>Hello</h1>","format":"png","viewport":{"width":1280,"height":720}}'
URL to PDF
curl -sS -X POST https://shot-api.com/api/v1/render \
  -H "authorization: Bearer sk_demo_local_dev_only" \
  -H "content-type: application/json" \
  -o page.pdf \
  -d '{"url":"https://example.com","format":"pdf"}'

Replace sk_demo_local_dev_only with a key from POST /api/v1/keys when demo mode is off.

GET /api/v1/usage

Requires a customer key. Returns key (id, name, prefix, plan), period (UTC YYYY-MM), used, limit, remaining, bytesOut, storage (file, memory, or upstash), persistent, and recent (up to 10 renders: source kind, format, status, bytes, duration). Target URLs and HTML are not stored.

Usage with X-API-Key
curl -sS https://shot-api.com/api/v1/usage \
  -H "x-api-key: sk_demo_local_dev_only"

POST /api/v1/keys

Mints a key. Requires SHOTAPI_ADMIN_TOKEN. Returns 201 with key, id, prefix, name, and plan. The full secret is in this response only. Body fields are optional: { "name": "acme", "plan": "demo" }. name defaults to API key (1–80 characters). plan is demo or pro, default demo. If the admin token is unset the route returns 503 admin_disabled.

curl -sS -X POST https://shot-api.com/api/v1/keys \
  -H "authorization: Bearer $SHOTAPI_ADMIN_TOKEN" \
  -H "content-type: application/json" \
  -d '{"name":"acme","plan":"demo"}'

POST /api/v1/demo/render

Open HTML preview used by the landing page. It exists only when demo mode is on (SHOTAPI_DEMO=1, or any process that is not running on Vercel). It accepts html only, up to 20 KB, and is limited to 30 requests a minute per instance. Over the limit it returns 429 rate_limited and Retry-After: 30. The render still counts against the seeded demo key. When demo mode is off the route returns 404 demo_disabled.

Billing and checkout

ShotAPI Pro is $19/month for 10,000 renders. Demo includes 100 renders. Quotas follow SHOTAPI_QUOTA_DEMO and SHOTAPI_QUOTA_PRO (defaults 100 and 10,000). Publish the Creem product, and Stripe's STRIPE_PRICE_ID when that sandbox is in use, at $19.

POST /api/v1/billing/checkout with a customer API key returns { "mode", "url" }. The provider is BILLING_PROVIDER when set (creem, paddle, stripe, or demo). Otherwise Creem is used when CREEM_API_KEY is set, then Stripe when STRIPE_SECRET_KEY is set, then demo.

  • In demo mode, mode is demo and url opens /billing/mock. Confirming that page, or POSTing { "token" } to /api/v1/billing/demo-confirm, sets the key's plan to pro. The token expires after 30 minutes. Demo confirm returns 404 when the billing provider is not demo.
  • With Creem, mode is creem. Checkout sends metadata.keyId and request_id equal to the API key id. Set CREEM_TEST_MODE=1 to call https://test-api.creem.io. Success returns to /billing/success. Point the webhook at POST /api/v1/billing/webhook/creem and set CREEM_WEBHOOK_SECRET. The route checks the creem-signature HMAC. checkout.completed and subscription.paid set the plan to pro and store the Creem customer and subscription ids. subscription.canceled and subscription.expired return the key to demo. A scheduled cancellation does not change the plan.
  • With Stripe, mode is stripe and url is a subscription Checkout session for STRIPE_PRICE_ID. Success returns to /billing/success; cancel returns to /billing/cancel. Set NEXT_PUBLIC_APP_URL so those URLs use the public origin. Point Stripe at POST /api/v1/billing/webhook and set STRIPE_WEBHOOK_SECRET. The webhook authenticates with Stripe-Signature, not an API key. checkout.session.completed upgrades the key to pro. customer.subscription.deleted, or an update whose status is canceled or unpaid, returns it to demo. Calling that webhook before Stripe is configured returns 501 billing_disabled.
  • BILLING_PROVIDER=paddle is reserved. Checkout returns 501 billing_disabled until that provider is implemented.
Start checkout
curl -sS -X POST https://shot-api.com/api/v1/billing/checkout \
  -H "authorization: Bearer sk_demo_local_dev_only"

If STRIPE_METER_EVENT_NAME is set and the key has a Stripe customer id, each successful render also sends a Billing Meter event. The payload keys default to stripe_customer_id and value (override with STRIPE_METER_CUSTOMER_PAYLOAD_KEY and STRIPE_METER_VALUE_PAYLOAD_KEY). A meter failure is logged and does not fail the render.

Errors

StatusCodeWhen
400invalid_requestBody failed validation, was not JSON, or included both or neither of url and html.
400invalid_url / blocked_urlURL is not public http(s), or the page requests a private host.
400invalid_tokenDemo checkout token is missing, invalid, or expired.
400invalid_signatureStripe or Creem webhook signature is missing or invalid.
401unauthorizedMissing or unknown API key, or the admin token does not match.
402quota_exceededMonthly render quota is used up.
404demo_disabledDemo preview was called while demo mode is off.
413payload_too_largeHTML, the request body, or the rendered file is too large.
429rate_limitedDemo preview exceeded 30 requests in a minute. Retry-After: 30.
500browser_missingChrome or serverless Chromium did not launch.
500billing_misconfiguredThe selected provider is missing a required setting, such as STRIPE_PRICE_ID, CREEM_PRODUCT_ID, or a valid BILLING_PROVIDER.
501billing_disabledStripe or Creem webhook secret is unset, or Paddle checkout was requested.
502 / 504render_failed / render_timeoutBrowser error, empty file, or the render exceeded the timeout. Checkout failures use billing_failed (502).
503admin_disabledPOST /api/v1/keys while SHOTAPI_ADMIN_TOKEN is unset.

Rate limits and hard caps

GET /api/v1/health

No API key. Returns ok, driver, browser, billing (demo, creem, creem_incomplete, stripe, stripe_incomplete, paddle, or invalid), storage, persistent, and demo.