Quickstart
From a raw screenshot to a store-ready App Store image with two API calls, in curl, Node.js or Python.
This guide takes one raw app screenshot and turns it into a 1290 × 2796 App Store image. It uses a free test key, so nothing is charged while you try it.
Before you start
- A raw screenshot of your app saved as
home.png(a simulator or device capture; PNG, JPEG or WebP). - One of:
curlandjq, Node.js 18 or later, or Python 3 with therequestspackage.
1. Create a test key
Sign in to the developer console, open API keys and create a
key with the Test environment. It starts with ss_test_ and is shown once: copy it into your
environment as STORESHOT_API_KEY. Keep keys out of source control and out of apps you ship.
Test keys run the whole API (uploads, validation, the queue, statuses, idempotency and result URLs) but never spend credits: each generation returns a sample image of exactly the requested size.
2. Upload a screenshot and generate an image
Pick your language. Each sample uploads home.png with POST /uploads, then creates one
generation with POST /generations and waits up to 60 seconds for it to finish.
curl
# STORESHOT_API_KEY holds your key (an ss_test_… key is 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"))3. Read the result
POST /generations answers 200 with the finished generation, or 202 when it is still running
after the wait. In that case poll GET /generations/{id} every few seconds until status is
succeeded, failed or canceled.
status: succeeded:result.image.urlis the PNG to download. The URL is signed and expires after 15 minutes; get the generation again for a fresh one.status: failed:error.codesays why (for exampleGENERATION_FAILED), and a live key’s credit is refunded automatically.
The Idempotency-Key header makes the request safe to retry: the same key and body return the
original generation instead of creating (or charging) a second one.
4. Go live
Create a key with the Live environment (ss_live_…) and use it in place of the test key. Live
generations cost credits: your first API key comes with 4 free credits, and the
pricing page lists plans and credit packs, which you buy in the
console. Without enough credits a request answers
402 INSUFFICIENT_CREDITS and nothing is created.
Next steps
- Authentication: scopes, creating and revoking keys, request IDs.
- Generations: every option, polling, idempotent retries, cancel and delete.
- Screenshot sets: a whole store listing in one call, as a ZIP in fastlane folders.
- Uploads: URL imports and presigned uploads.
- Webhooks: signed callbacks when a generation or set finishes.
- API reference: every operation, field and error code.