# Palettes API reference

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

Free brand palettes from your images. Contract 1.18.0; base URL `https://api.storeshot.art/v1`. Schemas: https://storeshot.art/docs/api/schemas.md

## POST /palettes

**Extract a brand palette** (operation `createPalette`). Auth: API key with scope `generations:write`, or access token.

Requires `generations:write`. Free: no credits, no AI model; the same result for the same image. `extract` (default) returns up to `maxColors` distinct colours of one image, exactly as the Storeshot app does; `dominant` returns the dominant colour of each image (an image that cannot be analysed is skipped). When no colour can be found, `source` is `default` and `colors` is the Storeshot default palette. URL inputs are fetched for this request only (same rules as `POST /uploads`) and never stored.

**When to use.** To get brand colours from a screenshot or logo before a set or generation (pass them as `palette`); optional, because a set extracts one by itself.

**Credits.** Free: no credits and no AI model (counts towards the request rate limit only).

**Request body** (`application/json`, required, schema `CreatePaletteRequest`):

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

Example request body:

```json
{
  "inputs": [
    {
      "uploadId": "6f1c1f0e-8a5e-4b8e-9a51-0f0e1d2c3b4a"
    }
  ],
  "mode": "extract",
  "maxColors": 5
}
```

**Responses:**

| Status | Description | Schema | Headers |
|---|---|---|---|
| `200` | The palette. | `Palette` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `400` | `INVALID_INPUT`. A credential sent in the query string is refused before authentication, without RateLimit headers. | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `401` | `UNAUTHORIZED`: missing, malformed, unknown, revoked or expired credential. | `ErrorEnvelope` | `X-Request-Id` |
| `403` | `FORBIDDEN`: see `details.reason`. | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `409` | `UPLOAD_NOT_READY`: see `details.reason`. | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `413` | `PAYLOAD_TOO_LARGE` (the request body is over its limit) or `IMAGE_TOO_LARGE` (see `details.reason`). | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `415` | `UNSUPPORTED_FORMAT`: not a PNG, JPEG or WebP image (see `details.reason`). | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `422` | `URL_FETCH_FAILED`: the URL could not be fetched (see `details.reason`). | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |
| `429` | `RATE_LIMITED`: the request rate of the API key (`details.scope` `key`) or of the account (`user`) is exhausted for this window, or too many failed authentication attempts came from this IP (no `details.scope` and no RateLimit headers). Retry after `Retry-After` seconds. | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy`, `Retry-After` |
| `503` | `SERVICE_UNAVAILABLE`: uploads are busy. Retry after `Retry-After` seconds. | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy`, `Retry-After` |
| `default` | Error (see ErrorEnvelope and /v1/docs/errors). | `ErrorEnvelope` | `X-Request-Id`, `RateLimit`, `RateLimit-Policy` |

Example 200 response:

```json
{
  "mode": "extract",
  "source": "extracted",
  "colors": [
    {
      "hex": "#2040A0",
      "name": "Steel Blue"
    },
    {
      "hex": "#E0E0E0",
      "name": "Silver"
    }
  ]
}
```
