# StoreShot API for developers

> HTML version: https://storeshot.art/developers/

JSON over HTTPS at `https://api.storeshot.art/v1`, described by an OpenAPI 3.1 document (https://api.storeshot.art/v1/openapi.json).
Reference: https://api.storeshot.art/v1/docs. Every response carries an `X-Request-Id`.

## Quickstart

1. Sign in to the developer console (https://console.storeshot.art/) and create a test key.
2. Upload a raw screenshot with `POST /uploads`.
3. Create a generation with `POST /generations` and `Prefer: wait=60`, or poll `GET /generations/{id}`.
4. Download `result.image.url` (signed for 15 minutes).

### curl

```sh
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

```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

```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 `Authorization: Bearer ss_live_…`. Keys are created in the console (or with `POST /api-keys`),
shown once and stored only as a hash.

- Live keys (`ss_live_…`) spend 1 credit per image, refunded automatically if it fails.
- Test keys (`ss_test_…`) are free and return sample images of exactly the requested size.
- Your first API key comes with 4 free credits.

## Main endpoints

Paths are relative to `https://api.storeshot.art/v1`.

### 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. Plans raise these (https://storeshot.art/pricing.md).
- 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: `{ "error": { "code", "message", "requestId", "docUrl" } }`. Codes: https://api.storeshot.art/v1/docs/errors
