Docs menu

Quickstart

From a raw screenshot to a store-ready App Store image with two API calls, in curl, Node.js or Python.

View as Markdown

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: curl and jq, Node.js 18 or later, or Python 3 with the requests package.

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.url is the PNG to download. The URL is signed and expires after 15 minutes; get the generation again for a fresh one.
  • status: failed: error.code says why (for example GENERATION_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.