# TypeScript SDK

> HTML version: https://storeshot.art/docs/sdk/

`@draggable/storeshot-sdk` is the official client for the StoreShot API. It is typed from the
[OpenAPI contract](/docs/api/), has no runtime dependencies and runs on Node.js 18 or later, Bun,
Deno and edge runtimes. It is published on
[npm](https://www.npmjs.com/package/@draggable/storeshot-sdk) under the Apache-2.0 licence, with its
source on [GitHub](https://github.com/Draggable/storeshot-sdk).

## Install

<!-- doctest: covered-by=packages/sdk/test-stack/sdk.stack.test.mjs -->

```sh
npm install @draggable/storeshot-sdk
```

## Set up the client

`new Storeshot()` reads the API key from the `STORESHOT_API_KEY` environment variable (or pass
`apiKey`). Start with a free test key: everything works, but the images are samples of the exact
size and no credits are used ([Test mode](/docs/test-mode/)). Live keys make the real images and
spend 1 credit per image. Call the API from your server or build script, never from a browser:
the SDK refuses to run in one by default, because every visitor could read the key.

On an edge runtime, pass the key explicitly: `new Storeshot({ apiKey: env.STORESHOT_API_KEY })`.
A tool without a key can get one with its human's approval: a client created with
`new Storeshot({ apiKey: null })` can call only `client.auth.*`, and `auth.linkAccount` runs the
device flow described in [Authentication](/docs/authentication/#link-an-agent-without-a-key).

## From one screenshot to a listing

From one raw screenshot (`home.png`) to an App Store listing in fastlane's folder layout:

<!-- doctest: covered-by=packages/sdk/test-stack/sdk.stack.test.mjs -->

```js
import { Storeshot } from '@draggable/storeshot-sdk';

const storeshot = new Storeshot(); // reads STORESHOT_API_KEY
const upload = await storeshot.uploads.create({ file: './home.png' });
const set = await storeshot.screenshotSets.create({
  app: { name: 'Trailmate' },
  screens: [{ input: { uploadId: upload.id }, caption: 'Find every trail' }],
  stores: [{ store: 'app_store' }],
});
const done = await storeshot.waitForScreenshotSet(set);
console.log(done.status, `${done.counts.succeeded}/${done.images} images`);
await storeshot.screenshotSets.downloadArchive(done.id, 'listing.zip', { layout: 'fastlane' });
```

Unzip `listing.zip` in your project root: fastlane `deliver` reads `fastlane/screenshots/`. With a
live key, ask for the price first: `await storeshot.screenshotSets.quote(body)` returns `images`,
`credits` and `sufficient` and creates nothing. For a single image use
`storeshot.generations.create()` and `storeshot.waitForGeneration()`; `storeshot.devices.list()`
lists the output sizes. See [Screenshot sets](/docs/screenshot-sets/) and
[Generations](/docs/generations/) for the request fields.

## What the client does for you

- **Waits:** `waitForGeneration` long-polls with `Prefer: wait`, `waitForScreenshotSet` polls
  every 3 seconds; both return the final resource (check `status`) and give up after 10 minutes
  unless you set `timeoutMs`.
- **Uploads and downloads:** `uploads.create` takes a file path, bytes, a `Blob`, a stream or a
  `url`, and checks the 20 MiB limit before sending ([Uploads](/docs/uploads/)).
  `downloadArchive` never leaves a partial file behind.
- **Retries:** `429` and busy `503` answers are retried after `Retry-After` for every request; other
  server and network errors only where repeating is safe (`GET`, creates with an
  `Idempotency-Key`, quotes). `generations.create` and `screenshotSets.create` always send an
  `Idempotency-Key`, so a retry never charges twice; pass your own `idempotencyKey` to make
  re-running a script safe too.
- **Errors:** every failure is a `StoreshotError` with the API `code`, `message`, `requestId` and
  `details` ([Errors](/docs/errors/)).
- **Webhooks:** `constructWebhookEvent` and `verifyWebhookSignature` check the
  `Storeshot-Signature` header against the raw body with Web Crypto ([Webhooks](/docs/webhooks/)).
- **Keys stay secret:** the SDK never logs the key and masks it in `toString()`, `JSON.stringify()`
  and error messages.

## Versions

The package version follows the StoreShot releases; `CONTRACT_VERSION` is the API contract its
types were generated from. The API changes only additively within a major version, so a newer API
keeps working with an older SDK. The README on npm has the full method reference, one method per
API operation, and the changelog lists what each version added.
