Developers
The StoreShot REST API
JSON over HTTPS at https://api.storeshot.art/v1, described by an OpenAPI 3.1 document. Every
response carries an X-Request-Id; every error has the same shape.
Quickstart
- Sign in to the developer console and create a test key.
- Upload a raw screenshot with
POST /uploads. -
Create a generation with
POST /generations and Prefer: wait=60,
or poll GET /generations/{id} every few seconds.
- Download
result.image.url (signed for 15 minutes).
curl
export STORESHOT_API_KEY=ss_test_… # free while you integrate
# 1. Upload a raw screenshot
UPLOAD=$(curl -sS -H "Authorization: Bearer $STORESHOT_API_KEY" \
-F file=@home.png https://api.storeshot.art/v1/uploads | jq -r .id)
# 2. Generate a 1290×2796 App Store image (waits up to 60 s)
curl -sS https://api.storeshot.art/v1/generations \
-H "Authorization: Bearer $STORESHOT_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: home-en-1' -H 'Prefer: wait=60' \
-d '{"inputs":[{"uploadId":"'$UPLOAD'"}],"store":"app_store",
"device":"app_store_1290x2796","app":{"name":"Trailmate"}}' \
| jq '{status, url: .result.image.url}'
Node.js
import { readFile } from 'node:fs/promises';
const api = 'https://api.storeshot.art/v1';
const headers = { Authorization: `Bearer ${process.env.STORESHOT_API_KEY}` };
const form = new FormData();
form.append('file', new Blob([await readFile('home.png')]), 'home.png');
const upload = await (await fetch(`${api}/uploads`, { method: 'POST', headers, body: form })).json();
const res = await fetch(`${api}/generations`, {
method: 'POST',
headers: { ...headers, 'Content-Type': 'application/json',
'Idempotency-Key': 'home-en-1', Prefer: 'wait=60' },
body: JSON.stringify({
inputs: [{ uploadId: upload.id }],
store: 'app_store',
device: 'app_store_1290x2796',
app: { name: 'Trailmate' },
}),
});
const generation = await res.json();
console.log(generation.status, generation.result?.image.url);
Python
import os, requests
api = "https://api.storeshot.art/v1"
h = {"Authorization": f"Bearer {os.environ['STORESHOT_API_KEY']}"}
upload = requests.post(f"{api}/uploads", headers=h,
files={"file": open("home.png", "rb")}).json()
generation = requests.post(
f"{api}/generations",
headers={**h, "Idempotency-Key": "home-en-1", "Prefer": "wait=60"},
json={"inputs": [{"uploadId": upload["id"]}], "store": "app_store",
"device": "app_store_1290x2796", "app": {"name": "Trailmate"}},
).json()
print(generation["status"], (generation["result"] or {}).get("image", {}).get("url"))
Authentication and test mode
Send your key as Authorization: Bearer ss_live_…. Keys are created and revoked in
the console (or with POST /api-keys), are shown once, and are stored only as a
hash.
- Live keys (
ss_live_…) run the real image models and spend
credits: 1 credit per image, refunded automatically if it fails.
- Test keys (
ss_test_…) behave the same (uploads, statuses,
idempotency, URLs) but are free and return sample images of exactly the requested size.
- Your first API key comes with 4 free credits.
Generations
One screenshot in, one store-ready image out, at exactly the size the store requires.
- POST
/generations Create a generation - GET
/generations/{id} Get a generation - GET
/generations List generations - POST
/generations/{id}/cancel Cancel a generation
Screenshot sets
A whole listing in one call: 1–10 screens on every device of one or both stores, plus an optional feature graphic, as a ZIP (Fastlane layout available).
- POST
/screenshot-sets Create a set (?dryRun=true quotes it) - GET
/screenshot-sets/{id} Get a screenshot set - POST
/screenshot-sets/{id}/retry Retry failed images - GET
/screenshot-sets/{id}/archive Download as a ZIP
Uploads
PNG, JPEG or WebP up to 20 MiB, as multipart, from a URL, or with a presigned upload.
- POST
/uploads Upload an image - POST
/uploads/presign Start a presigned upload - GET
/uploads/{id} Get an upload - DELETE
/uploads/{id} Delete an upload
Store helpers
Official screenshot sizes and rules per store, and brand palettes from a screenshot.
- GET
/devices List store screenshot sizes - GET
/stores/{store}/requirements Store screenshot requirements - POST
/palettes Extract a brand palette
Webhooks
Signed generation.succeeded, generation.failed, screenshot_set.completed and credits.low events.
- POST
/webhooks Create a webhook - POST
/webhooks/{id}/test Send a test event - GET
/webhooks/{id}/deliveries List deliveries
Account and billing
Keys, credit balance, usage, credit packs and monthly plans.
- GET
/me Who am I - POST
/api-keys Create an API key - GET
/credits Credit balance - GET
/usage Generation usage - GET
/billing/packs List credit packs - GET
/billing/plans List plans
Credits, limits and errors
-
Without credits a live request is
402 INSUFFICIENT_CREDITS; balance at
GET /credits, history at GET /usage.
-
Without a plan: 60 requests per minute per key,
3 generations in progress and 100 credits
per UTC day. Monthly plans raise these (pricing).
-
Responses carry
RateLimit and RateLimit-Policy headers; a
429 adds Retry-After.
-
Send an
Idempotency-Key with every create: retries return the original result
and are never charged twice.
-
Errors look like
{ "error": { "code", "message", "requestId", "docUrl" } }; see
the error codes.
Machine-readable: OpenAPI JSON · llms.txt ·
this page as Markdown.
Start with a free test key.
Sample images at the exact store sizes, no credits spent, the same API as live.