# StoreShot API schemas

> HTML version: https://storeshot.art/docs/api/schemas/

The 117 objects of the StoreShot API 1.18.0 contract.

## ApiKeyEnvironment

Type `string`. `test` keys never incur charges or call image providers.

One of: `"live"`, `"test"`.

## GenerationStatus

Type `string`. `queued`: waiting for a worker (can be canceled). `running`: being generated (retries keep it `running`). `succeeded`, `failed` and `canceled` are final.

One of: `"queued"`, `"running"`, `"succeeded"`, `"failed"`, `"canceled"`.

## StoreTarget

Type `string`.

One of: `"app_store"`, `"google_play"`.

## Orientation

Type `string`.

One of: `"portrait"`, `"landscape"`.

## PaletteColor

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `hex` | `string` | yes |  |
| `name` | `string` | yes |  |

## WebhookGenerationData

Type `object`. The `Generation` at the time of the event, without download URLs.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `GenerationStatus` | yes |  |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `inputs` | `array of object` | yes |  |
| `inputs[].uploadId` | `string` | yes | Format: uuid |
| `inputs[].sourceUrl` | `string`, nullable | yes | For a URL input: origin and path of the URL (it was imported as `uploadId`). |
| `store` | `StoreTarget` | yes |  |
| `device` | `string` | yes | Catalogue id of the size. |
| `size` | `object` | yes |  |
| `size.width` | `integer` | yes |  |
| `size.height` | `integer` | yes |  |
| `orientation` | `Orientation` | yes |  |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | yes |  |
| `caption` | `string`, nullable | yes | As requested; null = derive. |
| `palette` | `array of PaletteColor`, nullable | yes | As requested, or from the style (`style.applied` contains `palette`); null = extract from the screenshot. |
| `logo` | `object`, nullable | yes |  |
| `logo.uploadId` | `string` | yes | Format: uuid |
| `metadata` | `object` | yes |  |
| `creditsCharged` | `integer` | yes | 1 for live keys, 0 for test keys. |
| `creditsRefunded` | `integer` | yes | Refunded when the generation failed or was canceled before it started. |
| `result` | `object`, nullable | yes | As in `GET /generations/{id}` but without URLs (they expire): fetch the generation for fresh ones. |
| `result.image` | `object` | yes |  |
| `result.image.contentType` | `string` | yes | PNG; JPEG for the Google Play feature graphic. One of: "image/png", "image/jpeg" |
| `result.image.width` | `integer` | yes |  |
| `result.image.height` | `integer` | yes |  |
| `result.thumbnail` | `object`, nullable | yes |  |
| `result.thumbnail.contentType` | `string` | yes | One of: "image/webp" |
| `result.caption` | `string` | yes | The caption used: yours, or the one derived from the screenshot (may be empty). |
| `result.palette` | `array of PaletteColor` | yes | The colours used. |
| `error` | `object`, nullable | yes | Only when `failed`. See /v1/docs/errors for each code. |
| `error.code` | `string` | yes | One of: "GENERATION_FAILED", "GENERATION_TIMEOUT", "NOT_A_SCREENSHOT", "UPLOAD_NOT_READY", "INTERNAL_ERROR" |
| `error.message` | `string` | yes |  |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `startedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `deletedAt` | `string`, nullable | yes | Set by `DELETE`: the result files are gone, this record stays. |
| `screenshotSet` | `object` | no | Present only when this generation is an image of a screenshot set (`POST /screenshot-sets`). |
| `screenshotSet.id` | `string` | yes | Format: uuid |
| `screenshotSet.position` | `integer` | yes | The screen (1-based). |
| `screenshotSet.attempt` | `integer` | yes | 2 and up: a retry. |

## GenerationSucceededEvent

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Event id (`evt_…`), identical on every attempt: deduplicate on it. |
| `type` | `string` | yes | One of: "generation.succeeded" |
| `created` | `string` | yes | RFC 3339 UTC timestamp. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `apiVersion` | `string` | yes |  |
| `data` | `WebhookGenerationData` | yes |  |

## GenerationFailedEvent

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Event id (`evt_…`), identical on every attempt: deduplicate on it. |
| `type` | `string` | yes | One of: "generation.failed" |
| `created` | `string` | yes | RFC 3339 UTC timestamp. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `apiVersion` | `string` | yes |  |
| `data` | `WebhookGenerationData` | yes |  |

## ScreenshotSetStatus

Type `string`. Computed from the current image of every slot: `queued` (all queued), `running` (any running, or some finished and some not), `succeeded` (all succeeded), `partially_failed` (all finished, some succeeded), `canceled` (none succeeded, all canceled), `failed` (none succeeded, otherwise).

One of: `"queued"`, `"running"`, `"succeeded"`, `"partially_failed"`, `"failed"`, `"canceled"`.

## ScreenshotSetPreview

Type `object`. A thumbnail for lists (since 1.12.0): the most recently finished succeeded image of the current attempts, a device screenshot before the Google Play feature graphic. Only on `GET /screenshot-sets` and `GET /screenshot-sets/{id}`; absent from create, retry and cancel answers and from `screenshot_set.completed` events. `null` while no image has succeeded, or for a deleted set.

| Field | Type | Required | Description |
|---|---|---|---|
| `generationId` | `string` | yes | The image shown. Format: uuid |
| `store` | `StoreTarget` | yes |  |
| `device` | `string` | yes |  |
| `thumbnail` | `object` | yes |  |
| `thumbnail.url` | `string` | yes | Signed URL of the 512 px WebP preview (expires at `urlsExpireAt`). Format: uri |
| `thumbnail.contentType` | `string` | yes | One of: "image/webp" |
| `urlsExpireAt` | `string` | yes | 15 minutes after this response. Fetch the set again for a fresh URL. |

## WebhookScreenshotSetData

Type `object`. The `ScreenshotSet` at the time of the event, without `source`, `project` and `preview`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `ScreenshotSetStatus` | yes |  |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | yes |  |
| `orientation` | `Orientation` | yes |  |
| `palette` | `array of PaletteColor` | yes | The palette of every image: as requested, or from the style (`style.applied` contains `palette`), or extracted. |
| `paletteSource` | `string` | yes | `auto`: extracted from screen 1 when the set was created. One of: "given", "auto" |
| `logo` | `object`, nullable | yes |  |
| `logo.uploadId` | `string` | yes | Format: uuid |
| `metadata` | `object` | yes |  |
| `screens` | `array of object` | yes |  |
| `screens[].position` | `integer` | yes |  |
| `screens[].input` | `object` | yes |  |
| `screens[].input.uploadId` | `string` | yes | Format: uuid |
| `screens[].caption` | `string` | yes |  |
| `stores` | `array of object` | yes |  |
| `stores[].store` | `StoreTarget` | yes |  |
| `stores[].devices` | `array of string` | yes |  |
| `stores[].featureGraphic` | `object`, nullable | yes |  |
| `stores[].featureGraphic.screen` | `integer` | yes |  |
| `stores[].featureGraphic.caption` | `string` | yes |  |
| `images` | `integer` | yes |  |
| `counts` | `object` | yes |  |
| `counts.queued` | `integer` | yes |  |
| `counts.running` | `integer` | yes |  |
| `counts.succeeded` | `integer` | yes |  |
| `counts.failed` | `integer` | yes |  |
| `counts.canceled` | `integer` | yes |  |
| `creditsCharged` | `integer` | yes | Every image of the set, retries included (0 with a test key). |
| `creditsRefunded` | `integer` | yes |  |
| `children` | `array of object` | yes | The current image of every slot (store, device, screen). Result URLs: `GET /generations?screenshotSetId=` or the archive. |
| `children[].position` | `integer` | yes | The screen (1-based). |
| `children[].store` | `StoreTarget` | yes |  |
| `children[].device` | `string` | yes |  |
| `children[].size` | `object` | yes |  |
| `children[].size.width` | `integer` | yes |  |
| `children[].size.height` | `integer` | yes |  |
| `children[].featureGraphic` | `boolean` | yes |  |
| `children[].generationId` | `string` | yes | Format: uuid |
| `children[].attempt` | `integer` | yes |  |
| `children[].status` | `GenerationStatus` | yes |  |
| `children[].error` | `object`, nullable | yes |  |
| `children[].error.code` | `string` | yes | One of: "GENERATION_FAILED", "GENERATION_TIMEOUT", "NOT_A_SCREENSHOT", "UPLOAD_NOT_READY", "INTERNAL_ERROR" |
| `children[].error.message` | `string` | yes |  |
| `archiveUrl` | `string` | yes |  |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `deletedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `preview` | `ScreenshotSetPreview` | no |  |

## ScreenshotSetCompletedEvent

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Event id (`evt_…`), identical on every attempt: deduplicate on it. |
| `type` | `string` | yes | One of: "screenshot_set.completed" |
| `created` | `string` | yes | RFC 3339 UTC timestamp. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `apiVersion` | `string` | yes |  |
| `data` | `WebhookScreenshotSetData` | yes |  |

## CreditsLowEvent

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Event id (`evt_…`), identical on every attempt: deduplicate on it. |
| `type` | `string` | yes | One of: "credits.low" |
| `created` | `string` | yes | RFC 3339 UTC timestamp. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `apiVersion` | `string` | yes |  |
| `data` | `object` | yes |  |
| `data.balances` | `object` | yes |  |
| `data.balances.starter` | `integer` | yes |  |
| `data.balances.subscription` | `integer` | yes |  |
| `data.balances.purchased` | `integer` | yes |  |
| `data.total` | `integer` | yes |  |
| `data.threshold` | `integer` | yes |  |

## WebhookTestEvent

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Event id (`evt_…`), identical on every attempt: deduplicate on it. |
| `type` | `string` | yes | One of: "webhook.test" |
| `created` | `string` | yes | RFC 3339 UTC timestamp. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `apiVersion` | `string` | yes |  |
| `data` | `object` | yes |  |
| `data.webhookId` | `string` | yes | Format: uuid |
| `data.message` | `string` | yes |  |

## CheckStatus

Type `string`.

One of: `"up"`, `"down"`, `"not_configured"`.

## HealthReport

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `status` | `string` | yes | One of: "healthy", "degraded" |
| `version` | `string` | yes | Server release version. |
| `commit` | `string` | yes | Deployed git commit (short SHA). |
| `checks` | `object` | yes |  |
| `checks.database` | `CheckStatus` | yes |  |
| `checks.redis` | `CheckStatus` | yes |  |

## ErrorCode

Type `string`. Stable, machine-readable error code. See /v1/docs/errors.

One of: `"INVALID_INPUT"`, `"UNAUTHORIZED"`, `"FORBIDDEN"`, `"NOT_FOUND"`, `"METHOD_NOT_ALLOWED"`, `"PAYLOAD_TOO_LARGE"`, `"UPLOAD_NOT_READY"`, `"IDEMPOTENCY_CONFLICT"`, `"GENERATION_STATE_CONFLICT"`, `"SCREENSHOT_SET_STATE_CONFLICT"`, `"WEBHOOK_STATE_CONFLICT"`, `"BILLING_STATE_CONFLICT"`, `"IMAGE_TOO_LARGE"`, `"UNSUPPORTED_FORMAT"`, `"NOT_A_SCREENSHOT"`, `"URL_FETCH_FAILED"`, `"WEBHOOK_URL_REJECTED"`, `"INSUFFICIENT_CREDITS"`, `"RATE_LIMITED"`, `"CONCURRENCY_LIMITED"`, `"INTERNAL_ERROR"`, `"GENERATION_FAILED"`, `"SERVICE_UNAVAILABLE"`, `"BILLING_UNAVAILABLE"`, `"GENERATION_TIMEOUT"`.

## ErrorEnvelope

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | `object` | yes |  |
| `error.code` | `ErrorCode` | yes |  |
| `error.message` | `string` | yes | Human-readable message; safe to display. |
| `error.requestId` | `string` | yes | Equals the X-Request-Id response header. |
| `error.docUrl` | `string` | yes | Documentation for this error code. Format: uri |
| `error.details` | `object` | no | Code-specific structured details. |

## DeviceKind

Type `string`.

One of: `"screenshot"`, `"feature_graphic"`.

## Device

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Stable identifier: `<store>_<width>x<height>`. |
| `store` | `StoreTarget` | yes |  |
| `kind` | `DeviceKind` | yes |  |
| `label` | `string` | yes | Display name; may change. Do not parse. |
| `deviceHint` | `string` | yes | Example devices; may change. Do not parse. |
| `width` | `integer` | yes | Pixels. Portrait (canonical) for screenshots; landscape swaps width/height. |
| `height` | `integer` | yes | Pixels. |
| `orientations` | `array of Orientation` | yes |  |
| `deprecated` | `boolean` | no | `true`: still accepted (same output size) so existing requests keep working, but it breaks a store rule or is superseded. Never a default. Use `replacedBy`. Absent otherwise. |
| `replacedBy` | `string` | no | Deprecated devices only: the `GET /devices` id to use instead. |

## DeviceList

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Device` | yes |  |

## ComponentStatus

Type `string`. The worst known component state (`unknown` only if every component is).

One of: `"operational"`, `"degraded"`, `"outage"`, `"unknown"`.

## StatusReport

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `status` | `ComponentStatus` | yes |  |
| `updatedAt` | `string` | yes | When the state was computed (RFC 3339 UTC). |
| `windowSeconds` | `integer` | yes | The window judged, in seconds. |
| `components` | `array of object` | yes |  |
| `components[].id` | `string` | yes | One of: "api", "generation", "image_provider", "vision_provider" |
| `components[].name` | `string` | yes |  |
| `components[].status` | `ComponentStatus` | yes | `unknown`: no recent traffic to judge from, or the state could not be read. |

## ApiKeyScope

Type `string`. Permission an API key carries. Access tokens implicitly hold every scope.

One of: `"generations:write"`, `"generations:read"`, `"uploads:write"`, `"usage:read"`, `"keys:manage"`, `"billing:read"`, `"billing:write"`, `"webhooks:read"`, `"webhooks:write"`.

## Me

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `user` | `object` | yes |  |
| `user.id` | `string` | yes | Format: uuid |
| `user.displayName` | `string`, nullable | yes |  |
| `user.avatarUrl` | `string`, nullable | yes |  |
| `user.email` | `string` | no | Only when authenticated with an access token. |
| `user.createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `credential` | `object` | yes |  |
| `credential.type` | `string` | yes | One of: "user_token", "api_key" |
| `credential.environment` | `ApiKeyEnvironment` | yes |  |
| `credential.scopes` | `array of ApiKeyScope` | yes |  |
| `credential.apiKey` | `object` | no | Only when authenticated with an API key. Never the secret. |
| `credential.apiKey.id` | `string` | yes | Format: uuid |
| `credential.apiKey.name` | `string` | yes |  |
| `credential.apiKey.prefix` | `string` | yes |  |
| `credential.apiKey.expiresAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |

## ApiKey

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `name` | `string` | yes |  |
| `prefix` | `string` | yes | First characters of the key, for display. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `scopes` | `array of ApiKeyScope` | yes |  |
| `status` | `string` | yes | One of: "active", "expired" |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `lastUsedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `expiresAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |

## CreatedApiKey

Type `ApiKey & object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `name` | `string` | yes |  |
| `prefix` | `string` | yes | First characters of the key, for display. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `scopes` | `array of ApiKeyScope` | yes |  |
| `status` | `string` | yes | One of: "active", "expired" |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `lastUsedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `expiresAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `key` | `string` | yes | The full secret. Returned only in this response; store it now. |

## CreateApiKeyRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes | 1–64 characters after trimming; need not be unique. |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `scopes` | `array of ApiKeyScope` | no | Defaults to every scope except `keys:manage`, `billing:read`, `billing:write`, `webhooks:read` and `webhooks:write`. |
| `expiresAt` | `string` | no | Optional RFC 3339 expiry, in the future and at most 365 days ahead. Format: date-time |

## ApiKeyList

Type `object`. Non-revoked keys, newest first (at most 100).

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of ApiKey` | yes |  |

## DeviceAuthorization

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `device_code` | `string` | yes | Secret. Send it to `POST /auth/device/token`; never show it to anyone. |
| `user_code` | `string` | yes | Show it to the human: they confirm it in the console. |
| `verification_uri` | `string` | yes | Format: uri |
| `verification_uri_complete` | `string` | yes | The verification page with the code filled in. Format: uri |
| `expires_in` | `integer` | yes | Seconds until the codes expire. |
| `interval` | `integer` | yes | Seconds to wait between polls of `POST /auth/device/token`. |

## DeviceAuthorizationRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `client_name` | `string` | no | Your tool's name, shown to the human as an unverified label. 1–64 printable ASCII characters. When absent, `client_id` is used instead (generic OAuth libraries send one). |
| `scope` | `string` | no | Space-separated scopes for the new key. Default `generations:write generations:read uploads:write usage:read`. `keys:manage`, `billing:write` and `webhooks:write` cannot be requested. The human may narrow the list, or add `billing:read` and `webhooks:read`. |
| `environment` | `ApiKeyEnvironment` | no | Environment of the new key. Default `live`. |

## DeviceToken

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `access_token` | `string` | yes | The new API key (`ss_live_…` or `ss_test_…`). Returned once: store it now. |
| `token_type` | `string` | yes | One of: "Bearer" |
| `scope` | `string` | yes | Space-separated scopes the human approved (may be narrower than requested). |
| `key_id` | `string` | yes | Id of the new API key. Format: uuid |
| `environment` | `ApiKeyEnvironment` | yes |  |

## DeviceTokenError

Type `object`. OAuth 2.0 error body (RFC 6749 §5.2, RFC 8628 §3.5): the one deliberate exception to `ErrorEnvelope`. `X-Request-Id` is still in the response headers.

| Field | Type | Required | Description |
|---|---|---|---|
| `error` | `string` | yes | One of: "authorization_pending", "slow_down", "access_denied", "expired_token", "invalid_grant", "invalid_request", "unsupported_grant_type" |
| `error_description` | `string` | yes |  |

## DeviceTokenRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `grant_type` | `string` | yes | One of: "urn:ietf:params:oauth:grant-type:device_code" |
| `device_code` | `string` | yes |  |

## CreditBalances

Type `object`. Remaining credits. A caller with no credits reads all zeros.

| Field | Type | Required | Description |
|---|---|---|---|
| `balances` | `object` | yes |  |
| `balances.starter` | `integer` | yes |  |
| `balances.subscription` | `integer` | yes |  |
| `balances.purchased` | `integer` | yes |  |
| `total` | `integer` | yes | Sum of the three balances. |

## UsageGroupBy

Type `string`. `day`: one bucket per UTC day in the range, including empty days. `key`: per API key id (`none` for calls without a key). `source`: per surface (`mobile`, `api`, `web`, `mcp`, `cli`).

One of: `"day"`, `"key"`, `"source"`.

## UsageCounts

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `generations` | `integer` | yes | Generations started in the bucket. |
| `succeeded` | `integer` | yes | Completed. |
| `failed` | `integer` | yes | Failed before any credit was charged. |
| `refunded` | `integer` | yes | Charged, then refunded. |
| `pending` | `integer` | yes | Still running. |
| `creditsUsed` | `integer` | yes | Credits charged minus credits refunded for these generations. One generation can use several credits. |

## UsageBucket

Type `UsageCounts & object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `generations` | `integer` | yes | Generations started in the bucket. |
| `succeeded` | `integer` | yes | Completed. |
| `failed` | `integer` | yes | Failed before any credit was charged. |
| `refunded` | `integer` | yes | Charged, then refunded. |
| `pending` | `integer` | yes | Still running. |
| `creditsUsed` | `integer` | yes | Credits charged minus credits refunded for these generations. One generation can use several credits. |
| `key` | `string` | yes | The UTC day, API key id or source. |

## UsageReport

Type `object`. Live usage of the whole account (every key and surface; test keys are unbilled and not counted), bucketed by when each generation started. Refunds always count against the generation they belong to.

| Field | Type | Required | Description |
|---|---|---|---|
| `from` | `string` | yes | Resolved start (inclusive). |
| `to` | `string` | yes | Resolved end (exclusive). |
| `groupBy` | `UsageGroupBy` | yes |  |
| `data` | `array of UsageBucket` | yes | Buckets sorted by `key`. |
| `totals` | `UsageCounts` | yes |  |

## UploadStatus

Type `string`. `pending`: presigned, waiting for the file and `complete`. `ready`: usable in generations. `failed`: the file was rejected (it is deleted). `deleted`: deleted by you or by retention (metadata only).

One of: `"pending"`, `"ready"`, `"failed"`, `"deleted"`.

## UploadOrientation

Type `string`. From the EXIF-oriented size; `square` when equal.

One of: `"portrait"`, `"landscape"`, `"square"`.

## Upload

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `UploadStatus` | yes |  |
| `source` | `string` | yes | How the file arrived: a multipart upload, a presigned PUT, or a URL import. One of: "multipart", "presign", "url" |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `contentType` | `string` | yes | Detected from the file bytes (for a pending presigned upload: the declared type). One of: "image/png", "image/jpeg", "image/webp" |
| `bytes` | `integer` | yes | File size (declared, while pending). |
| `width` | `integer`, nullable | yes | Pixels, EXIF-oriented. Null unless ready. |
| `height` | `integer`, nullable | yes | Pixels, EXIF-oriented. Null unless ready. |
| `orientation` | `UploadOrientation` | yes |  |
| `sha256` | `string`, nullable | yes | Hex SHA-256 of the file. Null unless ready. |
| `sourceUrl` | `string`, nullable | yes | URL imports only: origin and path of the URL you sent (query and fragment are never stored). |
| `expiresAt` | `string` | yes | When the upload and its file are deleted: 30 days after creation (24 hours for pending or failed uploads). Generated results are kept until you delete them. |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |

## CreateUploadFromUrl

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | yes | An `https://` URL (port 443, no credentials, at most 2048 characters) that resolves to a public address. Fetched once, server-side. |

## PresignedUpload

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `upload` | `Upload` | yes |  |
| `uploadUrl` | `string` | yes | PUT the file here, from your server. Format: uri |
| `method` | `string` | yes | One of: "PUT" |
| `headers` | `object` | yes | Send exactly these headers with the PUT (they are signed). |
| `uploadUrlExpiresAt` | `string` | yes | 15 minutes after this response. |

## PresignUploadRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `contentType` | `string` | yes | The type of the file you will PUT: `image/png`, `image/jpeg` or `image/webp`. |
| `bytes` | `integer` | yes | Exact size of the file you will PUT, at most 20 MB (20971520). |

## UploadList

Type `object`. Uploads that are not deleted, newest first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Upload` | yes |  |
| `nextCursor` | `string`, nullable | yes | Pass as `cursor` for the next page; null on the last page. |

## GenerationResult

Type `object`. Only when `succeeded` and not deleted.

| Field | Type | Required | Description |
|---|---|---|---|
| `image` | `object` | yes |  |
| `image.url` | `string` | yes | Signed download URL (expires at `urlsExpireAt`). Format: uri |
| `image.contentType` | `string` | yes | PNG; JPEG for the Google Play feature graphic. One of: "image/png", "image/jpeg" |
| `image.width` | `integer` | yes |  |
| `image.height` | `integer` | yes |  |
| `thumbnail` | `object`, nullable | yes | A 512 px preview. Best effort: its URL may return 404. |
| `thumbnail.url` | `string` | yes | Format: uri |
| `thumbnail.contentType` | `string` | yes | One of: "image/webp" |
| `urlsExpireAt` | `string` | yes | 15 minutes after this response. Fetch the generation again for fresh URLs. |
| `caption` | `string` | yes | The caption used: yours, or the one derived from the screenshot (may be empty). |
| `palette` | `array of PaletteColor` | yes | The colours used. |

## GenerationSource

Type `string`. Since 1.15.0 (always sent): where the request that created it came from.

One of: `"mobile"`, `"api"`, `"web"`, `"mcp"`, `"cli"`.

## ProjectRef

Type `object`. Since 1.15.0 (always sent): its project, directly or through its screenshot set (`POST /projects/{id}/items`).

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `name` | `string` | yes |  |

## StyleField

Type `string`. A field a style can supply to a generation or screenshot set.

One of: `"palette"`, `"logo"`, `"backgroundMood"`, `"backgroundPrompt"`, `"captionTone"`.

## StyleRef

Type `object`. Since 1.18.0 (always sent): the style (`styleId`) it was made with, for an image of a screenshot set its set's; `null` when none.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string`, nullable | yes | `null` once the style was deleted (the generation keeps what it used). Format: uuid |
| `name` | `string` | yes |  |
| `applied` | `array of StyleField` | yes | What the style supplied, as it was at creation. Fields you sent yourself are not listed. |

## Generation

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `GenerationStatus` | yes |  |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `inputs` | `array of object` | yes |  |
| `inputs[].uploadId` | `string` | yes | Format: uuid |
| `inputs[].sourceUrl` | `string`, nullable | yes | For a URL input: origin and path of the URL (it was imported as `uploadId`). |
| `store` | `StoreTarget` | yes |  |
| `device` | `string` | yes | Catalogue id of the size. |
| `size` | `object` | yes |  |
| `size.width` | `integer` | yes |  |
| `size.height` | `integer` | yes |  |
| `orientation` | `Orientation` | yes |  |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | yes |  |
| `caption` | `string`, nullable | yes | As requested; null = derive. |
| `palette` | `array of PaletteColor`, nullable | yes | As requested, or from the style (`style.applied` contains `palette`); null = extract from the screenshot. |
| `logo` | `object`, nullable | yes |  |
| `logo.uploadId` | `string` | yes | Format: uuid |
| `metadata` | `object` | yes |  |
| `creditsCharged` | `integer` | yes | 1 for live keys, 0 for test keys. |
| `creditsRefunded` | `integer` | yes | Refunded when the generation failed or was canceled before it started. |
| `result` | `GenerationResult` | yes |  |
| `error` | `object`, nullable | yes | Only when `failed`. See /v1/docs/errors for each code. |
| `error.code` | `string` | yes | One of: "GENERATION_FAILED", "GENERATION_TIMEOUT", "NOT_A_SCREENSHOT", "UPLOAD_NOT_READY", "INTERNAL_ERROR" |
| `error.message` | `string` | yes |  |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `startedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `deletedAt` | `string`, nullable | yes | Set by `DELETE`: the result files are gone, this record stays. |
| `screenshotSet` | `object` | no | Present only when this generation is an image of a screenshot set (`POST /screenshot-sets`). |
| `screenshotSet.id` | `string` | yes | Format: uuid |
| `screenshotSet.position` | `integer` | yes | The screen (1-based). |
| `screenshotSet.attempt` | `integer` | yes | 2 and up: a retry. |
| `source` | `GenerationSource` | no |  |
| `clientName` | `string`, nullable | no | Since 1.15.0 (always sent): the `X-Storeshot-Client` header of the request (e.g. `cli/storeshot@1.2.0`), else its sanitised `User-Agent`. Self-asserted: for your records only. |
| `requestId` | `string`, nullable | no | Since 1.15.0 (always sent): the `X-Request-Id` of the request that created it (quote it to support). Null for test generations made before 1.15.0. |
| `project` | `ProjectRef` | no |  |
| `style` | `StyleRef` | no |  |

## GenerationInput

Type `object`. An image: an upload id or an https URL.

## GenerationSize

Type `object`. Output pixels: a catalogue size of the store (portrait, or swapped for landscape; the Google Play feature graphic is 1024×500 only).

| Field | Type | Required | Description |
|---|---|---|---|
| `width` | `integer` | yes |  |
| `height` | `integer` | yes |  |

## GenerationMetadata

Type `object`. Your own labels, echoed back and filterable (`GET /generations?metadata=key:value`): at most 20 keys of 1–40 characters (`A-Z a-z 0-9 _ . -`), string values of at most 500 characters, 4 KB in total.

## CreateGenerationRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `inputs` | `array of GenerationInput` | yes | Exactly one screenshot in this version. |
| `store` | `StoreTarget` | yes |  |
| `device` | `string` | no | A `GET /devices` id of the same store. Exactly one of `device` and `size` is required. |
| `size` | `GenerationSize` | no |  |
| `orientation` | `Orientation` | no | Default: from `size` (wider than tall is landscape), else portrait (the feature graphic: landscape). Must agree with the size and the device. |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | no | Default: "" |
| `caption` | `string` | no | The marketing caption. Absent or blank: derived from the screenshot. |
| `palette` | `array of PaletteColor` | no | 1–8 colours. Absent: extracted from the screenshot. |
| `logo` | `GenerationInput` | no | Your app icon or logo (any shape; PNG, JPEG or WebP). |
| `metadata` | `GenerationMetadata` | no |  |
| `projectId` | `string` | no | Since 1.15.0: put the generation in this project of yours (`GET /projects`). The project does not change how it is generated. Format: uuid |
| `styleId` | `string` | no | Since 1.18.0: a style of yours (`GET /styles`). It fills `palette` and `logo` when you leave them out and adds its background mood, description and caption tone. It is copied into the generation: editing or deleting the style later changes nothing here. Format: uuid |

## GenerationList

Type `object`. Generations that are not deleted, newest first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Generation` | yes |  |
| `nextCursor` | `string`, nullable | yes | Pass as `cursor` for the next page; null on the last page. |

## ScreenshotSetQuote

Type `object`. Nothing was created or charged. A quote is not a reservation.

| Field | Type | Required | Description |
|---|---|---|---|
| `dryRun` | `boolean` | yes | One of: true |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `images` | `integer` | yes |  |
| `credits` | `integer` | yes | What creating this set charges now (1 per image with a live key, 0 with a test key). |
| `availableCredits` | `integer` | yes |  |
| `sufficient` | `boolean` | yes |  |
| `screens` | `integer` | yes |  |
| `breakdown` | `array of object` | yes |  |
| `breakdown[].store` | `StoreTarget` | yes |  |
| `breakdown[].device` | `string` | yes |  |
| `breakdown[].size` | `object` | yes |  |
| `breakdown[].size.width` | `integer` | yes |  |
| `breakdown[].size.height` | `integer` | yes |  |
| `breakdown[].orientation` | `Orientation` | yes |  |
| `breakdown[].images` | `integer` | yes |  |
| `breakdown[].defaulted` | `boolean` | yes | The store default (no `devices` given). |

## ScreenshotSet

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `ScreenshotSetStatus` | yes |  |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | yes |  |
| `orientation` | `Orientation` | yes |  |
| `palette` | `array of PaletteColor` | yes | The palette of every image: as requested, or from the style (`style.applied` contains `palette`), or extracted. |
| `paletteSource` | `string` | yes | `auto`: extracted from screen 1 when the set was created. One of: "given", "auto" |
| `logo` | `object`, nullable | yes |  |
| `logo.uploadId` | `string` | yes | Format: uuid |
| `metadata` | `object` | yes |  |
| `screens` | `array of object` | yes |  |
| `screens[].position` | `integer` | yes |  |
| `screens[].input` | `object` | yes |  |
| `screens[].input.uploadId` | `string` | yes | Format: uuid |
| `screens[].caption` | `string` | yes |  |
| `stores` | `array of object` | yes |  |
| `stores[].store` | `StoreTarget` | yes |  |
| `stores[].devices` | `array of string` | yes |  |
| `stores[].featureGraphic` | `object`, nullable | yes |  |
| `stores[].featureGraphic.screen` | `integer` | yes |  |
| `stores[].featureGraphic.caption` | `string` | yes |  |
| `images` | `integer` | yes |  |
| `counts` | `object` | yes |  |
| `counts.queued` | `integer` | yes |  |
| `counts.running` | `integer` | yes |  |
| `counts.succeeded` | `integer` | yes |  |
| `counts.failed` | `integer` | yes |  |
| `counts.canceled` | `integer` | yes |  |
| `creditsCharged` | `integer` | yes | Every image of the set, retries included (0 with a test key). |
| `creditsRefunded` | `integer` | yes |  |
| `children` | `array of object` | yes | The current image of every slot (store, device, screen). Result URLs: `GET /generations?screenshotSetId=` or the archive. |
| `children[].position` | `integer` | yes | The screen (1-based). |
| `children[].store` | `StoreTarget` | yes |  |
| `children[].device` | `string` | yes |  |
| `children[].size` | `object` | yes |  |
| `children[].size.width` | `integer` | yes |  |
| `children[].size.height` | `integer` | yes |  |
| `children[].featureGraphic` | `boolean` | yes |  |
| `children[].generationId` | `string` | yes | Format: uuid |
| `children[].attempt` | `integer` | yes |  |
| `children[].status` | `GenerationStatus` | yes |  |
| `children[].error` | `object`, nullable | yes |  |
| `children[].error.code` | `string` | yes | One of: "GENERATION_FAILED", "GENERATION_TIMEOUT", "NOT_A_SCREENSHOT", "UPLOAD_NOT_READY", "INTERNAL_ERROR" |
| `children[].error.message` | `string` | yes |  |
| `archiveUrl` | `string` | yes |  |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `deletedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `preview` | `ScreenshotSetPreview` | no |  |
| `source` | `GenerationSource` | no | Since 1.15.0 (always sent; absent from `screenshot_set.completed` events): the source of the request that created the set. |
| `project` | `ProjectRef` | no | Since 1.15.0 (always sent; absent from `screenshot_set.completed` events): its project (`POST /projects/{id}/items`). |
| `style` | `StyleRef` | no | Since 1.18.0 (always sent; absent from `screenshot_set.completed` events): the style (`styleId`) it was made with; `null` when none. |

## SetUploadRef

Type `object`. An upload id (URL inputs are not accepted in sets).

| Field | Type | Required | Description |
|---|---|---|---|
| `uploadId` | `string` | yes | A `ready` upload of the same environment (test or live) as the credential. Format: uuid |

## ScreenshotSetScreen

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `input` | `SetUploadRef` | yes |  |
| `caption` | `string` | yes | Required (1–1000 characters): the same caption is used on every device of this screen. |

## ScreenshotSetFeatureGraphic

Type `boolean or object`. Google Play only. `true`: one 1024×500 feature graphic from screen 1 with its caption (+1 image).

## ScreenshotSetTarget

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `store` | `StoreTarget` | yes |  |
| `devices` | `array of string` | no | `GET /devices` screenshot ids of this store that meet its rules (`GET /stores/{store}/requirements`). Absent: the store defaults (App Store: `app_store_1290x2796` and `app_store_1179x2556`, the 6.9" and 6.3" iPhone groups Apple asks for; Google Play: `google_play_1080x1920`). At most one size per device class (for a taller Play phone, `google_play_1080x2160` instead of `google_play_1080x1920`). Pass fewer devices to pay for fewer images. |
| `featureGraphic` | `ScreenshotSetFeatureGraphic` | no |  |

## CreateScreenshotSetRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | no | Default: "" |
| `screens` | `array of ScreenshotSetScreen` | yes | 1–10 screenshots, in listing order. |
| `stores` | `array of ScreenshotSetTarget` | yes |  |
| `orientation` | `Orientation` | no | Of the screenshots (the feature graphic is always landscape). Default: "portrait" |
| `palette` | `array of PaletteColor` | no | 1–8 colours. Absent: extracted once from screen 1 and used for every image. |
| `logo` | `SetUploadRef` | no | Your app icon or logo, used on every image. |
| `metadata` | `GenerationMetadata` | no |  |
| `projectId` | `string` | no | Since 1.15.0: put the set (and so every image of it, retries included) in this project of yours. Format: uuid |
| `styleId` | `string` | no | Since 1.18.0: a style of yours (`GET /styles`). It fills `palette` and `logo` when you leave them out and adds its background mood, description and caption tone to every image. It is copied into the set and its retries: editing or deleting the style later changes nothing here. With `?dryRun=true` the style is checked; styles are free, so the quote is the same. Format: uuid |

## ScreenshotSetList

Type `object`. Sets that are not deleted, newest first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of ScreenshotSet` | yes |  |
| `nextCursor` | `string`, nullable | yes |  |

## ScreenshotSetRetryResult

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `created` | `array of object` | yes |  |
| `created[].generationId` | `string` | yes | Format: uuid |
| `created[].position` | `integer` | yes |  |
| `created[].device` | `string` | yes |  |
| `created[].attempt` | `integer` | yes |  |
| `skipped` | `array of object` | yes |  |
| `skipped[].generationId` | `string` | yes | Format: uuid |
| `skipped[].reason` | `string` | yes | One of: "superseded", "not_failed", "attempts_exhausted" |

## RetriedScreenshotSet

Type `ScreenshotSet & object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `ScreenshotSetStatus` | yes |  |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `app` | `object` | yes |  |
| `app.name` | `string` | yes |  |
| `app.description` | `string` | yes |  |
| `orientation` | `Orientation` | yes |  |
| `palette` | `array of PaletteColor` | yes | The palette of every image: as requested, or from the style (`style.applied` contains `palette`), or extracted. |
| `paletteSource` | `string` | yes | `auto`: extracted from screen 1 when the set was created. One of: "given", "auto" |
| `logo` | `object`, nullable | yes |  |
| `logo.uploadId` | `string` | yes | Format: uuid |
| `metadata` | `object` | yes |  |
| `screens` | `array of object` | yes |  |
| `screens[].position` | `integer` | yes |  |
| `screens[].input` | `object` | yes |  |
| `screens[].input.uploadId` | `string` | yes | Format: uuid |
| `screens[].caption` | `string` | yes |  |
| `stores` | `array of object` | yes |  |
| `stores[].store` | `StoreTarget` | yes |  |
| `stores[].devices` | `array of string` | yes |  |
| `stores[].featureGraphic` | `object`, nullable | yes |  |
| `stores[].featureGraphic.screen` | `integer` | yes |  |
| `stores[].featureGraphic.caption` | `string` | yes |  |
| `images` | `integer` | yes |  |
| `counts` | `object` | yes |  |
| `counts.queued` | `integer` | yes |  |
| `counts.running` | `integer` | yes |  |
| `counts.succeeded` | `integer` | yes |  |
| `counts.failed` | `integer` | yes |  |
| `counts.canceled` | `integer` | yes |  |
| `creditsCharged` | `integer` | yes | Every image of the set, retries included (0 with a test key). |
| `creditsRefunded` | `integer` | yes |  |
| `children` | `array of object` | yes | The current image of every slot (store, device, screen). Result URLs: `GET /generations?screenshotSetId=` or the archive. |
| `children[].position` | `integer` | yes | The screen (1-based). |
| `children[].store` | `StoreTarget` | yes |  |
| `children[].device` | `string` | yes |  |
| `children[].size` | `object` | yes |  |
| `children[].size.width` | `integer` | yes |  |
| `children[].size.height` | `integer` | yes |  |
| `children[].featureGraphic` | `boolean` | yes |  |
| `children[].generationId` | `string` | yes | Format: uuid |
| `children[].attempt` | `integer` | yes |  |
| `children[].status` | `GenerationStatus` | yes |  |
| `children[].error` | `object`, nullable | yes |  |
| `children[].error.code` | `string` | yes | One of: "GENERATION_FAILED", "GENERATION_TIMEOUT", "NOT_A_SCREENSHOT", "UPLOAD_NOT_READY", "INTERNAL_ERROR" |
| `children[].error.message` | `string` | yes |  |
| `archiveUrl` | `string` | yes |  |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `deletedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `preview` | `ScreenshotSetPreview` | no |  |
| `source` | `GenerationSource` | no | Since 1.15.0 (always sent; absent from `screenshot_set.completed` events): the source of the request that created the set. |
| `project` | `ProjectRef` | no | Since 1.15.0 (always sent; absent from `screenshot_set.completed` events): its project (`POST /projects/{id}/items`). |
| `style` | `StyleRef` | no | Since 1.18.0 (always sent; absent from `screenshot_set.completed` events): the style (`styleId`) it was made with; `null` when none. |
| `retry` | `ScreenshotSetRetryResult` | yes |  |

## RetryScreenshotSetRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `generationIds` | `array of string` | no | Images of this set to retry. Absent: every failed or canceled image. |

## Project

Type `object`. Groups generations and screenshot sets of one app. Its palette and logo are stored for you; generations do not inherit them (send them in each request).

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `name` | `string` | yes |  |
| `description` | `string` | yes |  |
| `store` | `StoreTarget` | yes |  |
| `palette` | `array of PaletteColor` | yes |  |
| `logo` | `object`, nullable | yes |  |
| `logo.url` | `string` | yes | Signed URL (expires at `expiresAt`). Format: uri |
| `logo.expiresAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `counts` | `object` | yes |  |
| `counts.generations` | `integer` | yes | Standalone generations in the project (the images of a set count in it). |
| `counts.screenshotSets` | `integer` | yes |  |
| `preview` | `ScreenshotSetPreview` | yes | The most recently finished succeeded image (with a thumbnail) of its generations and sets; `null` while there is none. |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |

## CreateProjectRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `description` | `string` | no |  |
| `store` | `StoreTarget` | no | Default `google_play`. |
| `palette` | `array of PaletteColor` | no |  |
| `logo` | `object` | no |  |
| `logo.uploadId` | `string` | yes | A `ready` upload of this account (PNG, JPEG or WebP; either environment). It is copied: deleting the upload later keeps the logo. Format: uuid |

## ProjectList

Type `object`. Projects, most recently updated first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Project` | yes |  |
| `nextCursor` | `string`, nullable | yes | Pass as `cursor` for the next page; null on the last page. |

## UpdateProjectRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | no |  |
| `description` | `string` | no |  |
| `store` | `StoreTarget` | no |  |
| `palette` | `array of PaletteColor` | no | Replaces the palette; `[]` clears it. |
| `logo` | `object`, nullable | no | A new logo, or `null` to remove it. |
| `logo.uploadId` | `string` | yes | A `ready` upload of this account (PNG, JPEG or WebP; either environment). It is copied: deleting the upload later keeps the logo. Format: uuid |

## ProjectItemsAdded

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `projectId` | `string` | yes | Format: uuid |
| `added` | `integer` | yes | Items newly in this project (moved from another project or from none). |

## ProjectItemsRequest

Type `object`. 1–100 ids in total. An API key can only use items of its own environment; an access token any.

| Field | Type | Required | Description |
|---|---|---|---|
| `generationIds` | `array of string` | no | Ids of your standalone generations (an image of a set moves with its set). |
| `screenshotSetIds` | `array of string` | no | Ids of your screenshot sets. |

## ProjectItemsRemoved

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `projectId` | `string` | yes | Format: uuid |
| `removed` | `integer` | yes | Items that left this project (items in another project are left alone). |

## BackgroundMood

Type `string`. The look of the background: `minimal`, `light`, `dark`, `vibrant`, `gradient` or `textured`.

One of: `"minimal"`, `"light"`, `"dark"`, `"vibrant"`, `"gradient"`, `"textured"`.

## CaptionTone

Type `string`. The voice of the headline: `professional`, `friendly`, `playful`, `bold` or `premium`. A caption you send is kept word for word; the tone guides its typography and any wording the model writes.

One of: `"professional"`, `"friendly"`, `"playful"`, `"bold"`, `"premium"`.

## Style

Type `object`. A brand kit: send its `id` as `styleId` when you create a generation or a screenshot set. It fills the palette and logo you leave out and adds its background mood, description and caption tone; fields you send win. Generations and sets keep a copy of what they used, so editing or deleting the style never changes them or their retries.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `name` | `string` | yes |  |
| `palette` | `array of PaletteColor` | yes |  |
| `logo` | `object`, nullable | yes |  |
| `logo.url` | `string` | yes | Signed URL (expires at `expiresAt`). Format: uri |
| `logo.expiresAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `backgroundMood` | `BackgroundMood` | yes |  |
| `backgroundPrompt` | `string`, nullable | yes |  |
| `captionTone` | `CaptionTone` | yes |  |
| `referenceGenerationId` | `string`, nullable | yes | The generation the values were copied from, if any (provenance only). Format: uuid |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |

## CreateStyleRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | yes |  |
| `palette` | `array of PaletteColor` | no | 1–8 colours. |
| `logo` | `object` | no |  |
| `logo.uploadId` | `string` | yes | A `ready` upload of this account (PNG, JPEG or WebP, at most 5 MB and 4096 × 4096 pixels; either environment). It is copied: deleting the upload later keeps the logo. Format: uuid |
| `backgroundMood` | `BackgroundMood & string` | no |  |
| `backgroundPrompt` | `string` | no | At most 200 characters on one line: what the background looks like. It is passed to the model as a quoted description of the look, never as instructions. |
| `captionTone` | `CaptionTone & string` | no |  |
| `referenceGenerationId` | `string` | no | A succeeded generation of yours: its colours (and, when it was made with a style, that style's background and caption choices) are copied into the fields you leave out, once. Its image and logo are not used. Format: uuid |

## StyleList

Type `object`. Styles, most recently updated first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Style` | yes |  |
| `nextCursor` | `string`, nullable | yes | Pass as `cursor` for the next page; null on the last page. |

## UpdateStyleRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` | no |  |
| `palette` | `array of PaletteColor` | no | Replaces the palette; `[]` clears it. |
| `logo` | `object`, nullable | no | A new logo, or `null` to remove it. |
| `logo.uploadId` | `string` | yes | A `ready` upload of this account (PNG, JPEG or WebP, at most 5 MB and 4096 × 4096 pixels; either environment). It is copied: deleting the upload later keeps the logo. Format: uuid |
| `backgroundMood` | `BackgroundMood` | no | `null` clears it. |
| `backgroundPrompt` | `string`, nullable | no | At most 200 characters on one line; `null` clears it. |
| `captionTone` | `CaptionTone` | no | `null` clears it. |
| `referenceGenerationId` | `string`, nullable | no | Copy from this generation again (into the fields you leave out), or `null` to forget where the values came from (the values stay). Format: uuid |

## ExtractedColor

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `hex` | `string` | yes | Uppercase `#RRGGBB`. |
| `name` | `string` | yes | Nearest named colour. |
| `inputIndex` | `integer` | no | `dominant` only: index in `inputs` of the image this colour came from. |

## Palette

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `mode` | `string` | yes | One of: "extract", "dominant" |
| `source` | `string` | yes | `default`: no colour could be extracted, so `colors` is the Storeshot default palette. One of: "extracted", "default" |
| `colors` | `array of ExtractedColor` | yes |  |

## PaletteInput

Type `object`. An image: an upload id or an https URL.

## CreatePaletteRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `inputs` | `array of PaletteInput` | yes | `extract`: exactly one image. `dominant`: 1–10 images. At most 3 URL inputs. |
| `mode` | `string` | no | `extract`: up to `maxColors` distinct colours of one image (the Storeshot app’s palette). `dominant`: the dominant colour of each image. One of: "extract", "dominant" Default: "extract" |
| `maxColors` | `integer` | no | `extract` only: 1–10, default 5. |

## StoreSummary

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `StoreTarget` | yes |  |
| `name` | `string` | yes |  |
| `requirementsUrl` | `string` | yes |  |

## StoreList

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of StoreSummary` | yes |  |

## StoreRuleIssue

Type `string`.

One of: `"size_not_accepted"`, `"edge_below_min"`, `"edge_above_max"`, `"aspect_ratio_exceeds_2_1"`, `"aspect_ratio_not_exact"`.

## StoreDeviceClass

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes |  |
| `family` | `string` | yes | One of: "iphone", "ipad", "watch", "phone", "tablet", "chromebook", "xr" |
| `label` | `string` | yes |  |
| `requirement` | `string` | yes | `conditional`: named by a `requiredGroups` entry (one of its classes is needed when its condition holds); `optional`: never needed. One of: "required", "conditional", "optional" |
| `count` | `object` | yes | Screenshots per locale when this class is provided. |
| `count.min` | `integer` | yes |  |
| `count.max` | `integer` | yes |  |
| `maxFileBytes` | `integer`, nullable | yes | Per-class file limit; null when the store publishes none. |
| `acceptedSizes` | `array of object`, nullable | yes | App Store: every accepted pixel size (both orientations). Null when `rules` apply. |
| `acceptedSizes[].width` | `integer` | yes |  |
| `acceptedSizes[].height` | `integer` | yes |  |
| `rules` | `object`, nullable | yes | Google Play: dimension rules. Null when `acceptedSizes` apply. |
| `rules.minEdge` | `integer` | yes | Shorter side, at least. |
| `rules.maxEdge` | `integer`, nullable | yes | Longer side, at most (null: no published maximum). |
| `rules.maxAspectRatio` | `number` | yes | Longer side ÷ shorter side, at most. |
| `rules.exactAspectRatio` | `string`, nullable | yes | `long:short` the image must match exactly, or null. |
| `rules.recommendedAspectRatios` | `array of string` | yes | Advisory (store promotion), not enforced. |
| `sources` | `array of string` | yes |  |
| `devices` | `array of object` | yes | `GET /devices` sizes of this class. `compliant: false` lists the hard store rules the size breaks (in portrait or landscape). A deprecated size (`deprecated: true`) names its `replacedBy`. |
| `devices[].id` | `string` | yes |  |
| `devices[].compliant` | `boolean` | yes |  |
| `devices[].issues` | `array of StoreRuleIssue` | yes |  |
| `devices[].deprecated` | `boolean` | no | `true`: still accepted (same output size) so existing requests keep working, but it breaks a store rule or is superseded. Never a default. Use `replacedBy`. Absent otherwise. |
| `devices[].replacedBy` | `string` | no | Deprecated devices only: the `GET /devices` id to use instead. |

## StoreRequirements

Type `object`. What a store needs from screenshots, from the official pages in `sources` (checked on `checkedOn`). Stores change their rules; treat this as guidance.

| Field | Type | Required | Description |
|---|---|---|---|
| `store` | `StoreTarget` | yes |  |
| `name` | `string` | yes |  |
| `screenshots` | `object` | yes |  |
| `screenshots.formats` | `array of string` | yes |  |
| `screenshots.alpha` | `boolean` | yes | false: no transparency (no alpha channel). |
| `screenshots.maxFileBytes` | `integer`, nullable | yes | Null when the store publishes no limit (see a class’s own `maxFileBytes`). |
| `screenshots.count` | `object` | yes |  |
| `screenshots.count.minTotal` | `integer` | yes | Across all classes. |
| `screenshots.count.maxPerDeviceClass` | `integer` | yes |  |
| `screenshots.sources` | `array of string` | yes |  |
| `deviceClasses` | `array of StoreDeviceClass` | yes |  |
| `requiredGroups` | `array of object` | yes |  |
| `requiredGroups[].when` | `string` | yes |  |
| `requiredGroups[].anyOf` | `array of string` | yes | Provide at least one of these classes. |
| `requiredGroups[].sources` | `array of string` | yes |  |
| `featureGraphic` | `object`, nullable | yes | Google Play only. |
| `featureGraphic.width` | `integer` | yes |  |
| `featureGraphic.height` | `integer` | yes |  |
| `featureGraphic.formats` | `array of string` | yes |  |
| `featureGraphic.alpha` | `boolean` | yes |  |
| `featureGraphic.maxFileBytes` | `integer`, nullable | yes |  |
| `featureGraphic.required` | `boolean` | yes |  |
| `featureGraphic.sources` | `array of string` | yes |  |
| `featureGraphic.device` | `string` | yes |  |
| `sources` | `array of object` | yes |  |
| `sources[].id` | `string` | yes | One of: "apple_screenshot_specs", "play_preview_assets" |
| `sources[].title` | `string` | yes |  |
| `sources[].url` | `string` | yes | Format: uri |
| `sources[].checkedOn` | `string` | yes | Date the facts were last checked (UTC). |

## WebhookEventType

Type `string`. `generation.succeeded`; `generation.failed` (a generation ended `failed` or `canceled`: see `data.status`); `screenshot_set.completed` (a set reached a final status, again after a retry); `credits.low` (live only: the balance fell below `creditsLowThreshold`). `webhook.test` is sent on demand by `POST /webhooks/{id}/test` and needs no subscription.

One of: `"generation.succeeded"`, `"generation.failed"`, `"screenshot_set.completed"`, `"credits.low"`.

## Webhook

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `url` | `string` | yes | Masked: scheme, host and the last 4 characters of the path. |
| `events` | `array of WebhookEventType` | yes |  |
| `description` | `string` | yes |  |
| `enabled` | `boolean` | yes |  |
| `creditsLowThreshold` | `integer` | yes |  |
| `secret` | `object` | yes |  |
| `secret.hint` | `string` | yes | The last 4 characters. |
| `secret.rotatedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `secret.previousExpiresAt` | `string`, nullable | yes | Until then deliveries are also signed with the previous secret. |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |

## WebhookWithSecret

Type `Webhook & object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `environment` | `ApiKeyEnvironment` | yes |  |
| `url` | `string` | yes | Masked: scheme, host and the last 4 characters of the path. |
| `events` | `array of WebhookEventType` | yes |  |
| `description` | `string` | yes |  |
| `enabled` | `boolean` | yes |  |
| `creditsLowThreshold` | `integer` | yes |  |
| `secret` | `object` | yes |  |
| `secret.hint` | `string` | yes | The last 4 characters. |
| `secret.rotatedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `secret.previousExpiresAt` | `string`, nullable | yes | Until then deliveries are also signed with the previous secret. |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `updatedAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `secret` | `object` | yes |  |
| `secret.hint` | `string` | yes | The last 4 characters. |
| `secret.rotatedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `secret.previousExpiresAt` | `string`, nullable | yes | Until then deliveries are also signed with the previous secret. |
| `secret.value` | `string` | yes | The signing secret (`whsec_…`). Returned only in this response; store it now. Verify `Storeshot-Signature` with it. |

## CreateWebhookRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | yes | An `https://` URL on port 443, without credentials, at most 2048 characters, whose host resolves only to public addresses (checked now and before every delivery). Shown masked afterwards. |
| `events` | `array of WebhookEventType` | yes | The business events to deliver (1–4, unique). |
| `description` | `string` | no | Free text, at most 200 characters. |
| `enabled` | `boolean` | no | Default `true`. |
| `creditsLowThreshold` | `integer` | no | Default 5. |
| `environment` | `ApiKeyEnvironment` | no | Which events the webhook receives: `live` or `test` (test-key generations and sets). Default: the API key environment (access tokens: `live`). An API key can only create webhooks of its own environment. |

## WebhookList

Type `object`. Webhooks, newest first (at most 10 per environment).

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of Webhook` | yes |  |

## UpdateWebhookRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | no | An `https://` URL on port 443, without credentials, at most 2048 characters, whose host resolves only to public addresses (checked now and before every delivery). Shown masked afterwards. |
| `events` | `array of WebhookEventType` | no | The business events to deliver (1–4, unique). |
| `description` | `string` | no | Free text, at most 200 characters. |
| `enabled` | `boolean` | no |  |
| `creditsLowThreshold` | `integer` | no | `credits.low` fires when the live balance falls below this many credits (1–10000). |

## RotateWebhookSecretRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `gracePeriodSeconds` | `integer` | no | How long the previous secret keeps signing deliveries next to the new one (0–604800, default 86400). 0 drops it at once. |

## WebhookTestResult

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `eventId` | `string` | yes |  |
| `deliveryId` | `string` | yes | Format: uuid |

## WebhookDeliveryStatus

Type `string`. `pending`: queued or retrying. `succeeded`: a 2xx answer. `exhausted`: 8 attempts failed. `failed`: the destination was refused (`blocked_destination`, `invalid_url`) or the secret could not be read. `canceled`: the webhook was disabled before the attempt.

One of: `"pending"`, `"succeeded"`, `"exhausted"`, `"failed"`, `"canceled"`.

## WebhookDelivery

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `eventId` | `string` | yes |  |
| `eventType` | `string` | yes | One of: "generation.succeeded", "generation.failed", "screenshot_set.completed", "credits.low", "webhook.test" |
| `status` | `WebhookDeliveryStatus` | yes |  |
| `attempts` | `integer` | yes |  |
| `nextAttemptAt` | `string`, nullable | yes | When `pending`: the next attempt is due then (null while one is in flight). |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `finishedAt` | `string`, nullable | yes | RFC 3339 UTC timestamp. |
| `log` | `array of object` | yes |  |
| `log[].attempt` | `integer` | yes |  |
| `log[].at` | `string` | yes | RFC 3339 UTC timestamp. |
| `log[].latencyMs` | `integer` | yes |  |
| `log[].statusCode` | `integer`, nullable | yes |  |
| `log[].errorClass` | `string`, nullable | yes | One of: "http_status", "timeout", "connect_failed", "tls_error", "dns_failure", "blocked_destination", "redirect", "invalid_url", "secret_unavailable" |
| `log[].responseSnippet` | `string`, nullable | yes | First 256 characters of the response body, control characters removed. |

## WebhookDeliveryList

Type `object`. Deliveries of this webhook, newest first.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of WebhookDelivery` | yes |  |
| `nextCursor` | `string`, nullable | yes |  |

## ActivityEntry

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `type` | `string` | yes | `api_key.created`, `api_key.revoked`, `credits.granted`, `credits.removed`, `webhook.created`, `webhook.updated`, `webhook.deleted`, `webhook.secret_rotated`, `webhook.test_sent`, `console.signed_in`, `console.sessions_revoked`. New types may be added. |
| `createdAt` | `string` | yes | RFC 3339 UTC timestamp. |
| `actor` | `object` | yes |  |
| `actor.type` | `string` | yes | `user`: you (console or access token); `api_key`: one of your keys; `system`: Storeshot (payments, plan renewals). One of: "user", "api_key", "system" |
| `actor.apiKeyId` | `string`, nullable | yes | Format: uuid |
| `actor.apiKeyPrefix` | `string`, nullable | yes |  |
| `target` | `object`, nullable | yes | What changed (`api_key`, `webhook`, `polar_order`, …). |
| `target.type` | `string` | yes |  |
| `target.id` | `string` | yes |  |
| `details` | `object` | yes | Type-specific, never secret: key name/prefix/environment, credits/bucket/reason, webhook host/events/changed fields, sign-in method. |
| `requestId` | `string`, nullable | yes | The `X-Request-Id` of the request that made the change (null for Storeshot). |

## ActivityList

Type `object`. Account activity, newest first (kept 400 days).

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of ActivityEntry` | yes |  |
| `nextCursor` | `string`, nullable | yes | Pass as `cursor` for the next page; null on the last page. |

## ActivityFamily

Type `string`. Only one family of types (`api_key`, `credits`, `webhook`, `console`).

One of: `"api_key"`, `"credits"`, `"webhook"`, `"console"`.

## BillingPack

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Pack id to send as `pack`. |
| `name` | `string` | yes |  |
| `credits` | `integer` | yes | Purchased credits (never expire). |
| `priceAmount` | `integer` | yes | List price in minor units (cents). Taxes are applied at checkout. |
| `currency` | `string` | yes | ISO 4217, lowercase. |

## BillingPackList

Type `object`. Packs on sale, cheapest first. Empty while web payments are switched off.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of BillingPack` | yes |  |

## BillingCheckout

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `status` | `string` | yes | `open` (send the buyer to `url`), `processing` (payment confirmed, credits on the way), `paid` (credits granted), `partially_refunded`, `refunded`, `expired` (not paid in time) or `failed`. One of: "open", "processing", "paid", "partially_refunded", "refunded", "expired", "failed" |
| `pack` | `string` | yes |  |
| `credits` | `integer` | yes |  |
| `priceAmount` | `integer` | yes | Minor units (cents). |
| `currency` | `string` | yes |  |
| `url` | `string`, nullable | yes | Hosted checkout page (Polar). Null unless `status` is `open`. Format: uri |
| `expiresAt` | `string`, nullable | yes | Format: date-time |
| `createdAt` | `string` | yes | Format: date-time |
| `paidAt` | `string`, nullable | yes | Format: date-time |
| `creditsGranted` | `integer` | yes | Credits added to `purchased`. |
| `creditsReversed` | `integer` | yes | Credits taken back after a refund (spent credits are not taken twice). |
| `kind` | `string` | no | SS-012: present (`subscription`) only on plan checkouts; then `pack` and `plan` hold the plan id and the credits are the monthly allotment, added to `subscription`. Absent on pack checkouts. One of: "subscription" |
| `plan` | `string` | no | SS-012: the plan of a plan checkout (absent on pack checkouts). One of: "pro", "agency", "scale" |
| `planName` | `string` | no | SS-013: the plan name, on `GET /billing/checkouts/{id}` of a plan checkout only (absent on pack checkouts). |
| `subscriptionStatus` | `string`, nullable | no | SS-013: the payment provider's status of the subscription this plan checkout created (`active`, `trialing`, `past_due`, `canceled`, …), or null while none is linked yet. Only on `GET /billing/checkouts/{id}` of a plan checkout. |

## CreateBillingCheckoutRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `pack` | `string` | yes |  |
| `successUrl` | `string` | no | Where the buyer lands after paying. Default: a static confirmation page. `https://` on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters. Format: uri |
| `returnUrl` | `string` | no | Where "back" on the checkout page leads. Default: the credits page. `https://` on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters. Format: uri |

## BillingPlanLimits

Type `object`. The limits that apply on a plan.

| Field | Type | Required | Description |
|---|---|---|---|
| `requestsPerMinutePerKey` | `integer` | yes |  |
| `requestsPerMinutePerUser` | `integer` | yes |  |
| `concurrentGenerations` | `integer` | yes |  |
| `dailyGenerations` | `integer` | yes | Credits per UTC day. |
| `maxStoredUploads` | `integer` | yes |  |
| `maxStoredUploadMegabytes` | `integer` | yes |  |

## BillingPlan

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Plan id to send as `plan`. One of: "pro", "agency", "scale" |
| `name` | `string` | yes |  |
| `credits` | `integer` | yes | Credits added to `subscription` each paid month. They are used before any other credits and expire (reset) at renewal; pack credits never expire. |
| `priceAmount` | `integer` | yes | Monthly list price in minor units (cents). Taxes are applied at checkout. |
| `currency` | `string` | yes | ISO 4217, lowercase. |
| `interval` | `string` | yes | One of: "month" |
| `limits` | `BillingPlanLimits` | yes |  |

## BillingPlanList

Type `object`. Plans on sale, cheapest first. Empty while plans are switched off.

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | `array of BillingPlan` | yes |  |

## BillingSubscription

Type `object`. The latest subscription of the account, or null.

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | yes | Format: uuid |
| `plan` | `string`, nullable | yes | Null when the subscription moved to a product that is not a Storeshot plan. One of: "pro", "agency", "scale" |
| `status` | `string` | yes | The payment provider status: `active`, `trialing`, `past_due`, `incomplete`, `incomplete_expired`, `unpaid`, `paused` or `canceled`. |
| `cancelAtPeriodEnd` | `boolean` | yes |  |
| `currentPeriodStart` | `string`, nullable | yes | Format: date-time |
| `currentPeriodEnd` | `string`, nullable | yes | Format: date-time |
| `pendingPlan` | `string`, nullable | yes | A downgrade that takes effect at the next renewal. One of: "pro", "agency", "scale" |
| `creditsPerPeriod` | `integer` | yes |  |
| `subscriptionCredits` | `integer` | yes | The `subscription` balance now (expires at renewal). |

## BillingSubscriptionState

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `plan` | `string` | yes | The tier whose limits apply now. A plan applies while its subscription is active (until one day after the period end) or past due (three days after the period end). One of: "free", "pro", "agency", "scale" |
| `limits` | `BillingPlanLimits` | yes |  |
| `subscription` | `BillingSubscription` | yes |  |

## CreateSubscriptionCheckoutRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `plan` | `string` | yes | One of: "pro", "agency", "scale" |
| `successUrl` | `string` | no | Where the buyer lands after paying. Default: a static confirmation page. `https://` on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters. Format: uri |
| `returnUrl` | `string` | no | Where "back" on the checkout page leads. Default: the credits page. `https://` on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters. Format: uri |

## ChangeSubscriptionRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `plan` | `string` | yes | One of: "pro", "agency", "scale" |

## BillingPortalSession

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `url` | `string` | yes | Open it in the browser of the account owner. Format: uri |
| `expiresAt` | `string`, nullable | yes | When a `session` link stops working; null for `email_login`. Format: date-time |
| `mode` | `string` | yes | `session`: signed in as this account. `email_login`: the provider asks for the billing email and sends a one-time code. One of: "session", "email_login" |

## CreatePortalSessionRequest

Type `object`.

| Field | Type | Required | Description |
|---|---|---|---|
| `returnUrl` | `string` | no | Where "back" in the portal leads. Default: the credits page. `https://` on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters. Format: uri |
