# Colors

A result's colors: read them, recolor it, keep your brand's palettes.

### The result's paint map

`GET /v1/conversions/{id}/paints`

Every tier, like the preview: the paints the file draws with its ground left out, in index order, each with its share of the frame, the ground, and a link to `paints.bin`: zlib of an index plane then a weight plane (width × height × 4 bytes each: every pixel's four largest paints, 255 = none, and their weights × 255). A pixel of any recolor is Σ w·c′ (premultiplied), with the ground under it as (1 − Σ w)·ground. Made on the first ask; a drawing of more than 24 paints has none.

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |

```bash
curl https://api.vecsnap.com/v1/conversions/00000000-0000-4000-8000-000000000000/paints \
  -H "Authorization: Bearer $VECSNAP_API_KEY"
```

Answer 200:

```json
{
  "width": 1,
  "height": 1,
  "ground": "#1f6feb",
  "groundArea": 0,
  "paints": [
    {
      "key": "#1f6feb",
      "area": 0
    }
  ],
  "map": "https://files.vecsnap.com/…",
  "expiresIn": 1
}
```

Answer 404:

```json
{
  "error": {
    "code": "not_found",
    "message": "There is nothing at that id for this caller.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 409:

```json
{
  "error": {
    "code": "conversion_failed",
    "message": "The conversion ended without a result.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 429:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests in a short time.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `not_found` | Check the id. Another account's objects read as not found too. |
| 409 | `conversion_failed` | Convert the image again. Converting is free. |
| 409 | `not_in_test_mode` | Use a live key for it, or test the conversion and its SVG files. |
| 409 | `not_ready` | Wait for it to finish (a webhook, the events stream, or a read), then ask again. |
| 409 | `result_outdated` | Convert the image again. |
| 409 | `too_many_colors` | Redraw it with fewer colors first. |
| 429 | `rate_limited` | Wait the Retry-After seconds, then send it again. |
| 502 | `derive_failed` | Try again. The coarser picture still shows. |

Every other refusal it may answer: `invalid_id` (400), `invalid_api_key` (401), `unauthenticated` (401), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `internal_error` (500), `auth_unavailable` (503).

### Preview fewer colors

`GET /v1/conversions/{id}/palette`

Previews of a redraw with fewer colors. Without `count`: the WHOLE rail in one answer (`counts`), every count in the engine's bounds, colors only: what a client asks once, then reads. With `count`: the colors the engine REACHES for at most that many (from the result's kept original; it can be fewer), and the result's own colors MERGED into them (`mapping`), a recolor of the very drawing a client draws exactly. With `format` (a web file with a picture), also `picture`: the merged drawing at the preview's size; its deeper tiles are POST /conversions/{id}/tiles with `palette`. The redraw itself (POST /images/{id}/conversions with `options.maxColors`) traces new shapes.

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |
| `count` | query | integer, 2 to 24 | no | One count; left out, the whole rail. |
| `format` | query | string | no | With `count`, a web file's id: its merged picture too. |

```bash
curl https://api.vecsnap.com/v1/conversions/00000000-0000-4000-8000-000000000000/palette \
  -H "Authorization: Bearer $VECSNAP_API_KEY"
```

Answer 200:

```json
{
  "counts": [
    {
      "asked": 0,
      "count": 1,
      "colors": [
        "#1f6feb"
      ],
      "mapping": {}
    }
  ]
}
```

Answer 404:

```json
{
  "error": {
    "code": "not_found",
    "message": "There is nothing at that id for this caller.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 409:

```json
{
  "error": {
    "code": "conversion_failed",
    "message": "The conversion ended without a result.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 429:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests in a short time.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `not_found` | Check the id. Another account's objects read as not found too. |
| 409 | `conversion_failed` | Convert the image again. Converting is free. |
| 409 | `not_in_test_mode` | Use a live key for it, or test the conversion and its SVG files. |
| 409 | `not_ready` | Wait for it to finish (a webhook, the events stream, or a read), then ask again. |
| 409 | `original_not_kept` | Upload the file again. |
| 409 | `result_outdated` | Convert the image again. |
| 429 | `rate_limited` | Wait the Retry-After seconds, then send it again. |
| 502 | `derive_failed` | Try again. The coarser picture still shows. |

Every other refusal it may answer: `format_unavailable` (400), `invalid_id` (400), `invalid_options` (400), `invalid_request` (400), `invalid_api_key` (401), `unauthenticated` (401), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `internal_error` (500), `auth_unavailable` (503).

### List a result's recolors

`GET /v1/conversions/{id}/variants`

The result's recolors, the last used first: the 10 used last, in one page (`hasMore` says older ones exist; they are not listed, and saving one again brings it first).

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |

```bash
curl https://api.vecsnap.com/v1/conversions/00000000-0000-4000-8000-000000000000/variants \
  -H "Authorization: Bearer $VECSNAP_API_KEY"
```

Answer 200:

```json
{
  "object": "list",
  "data": [
    {
      "id": "00000000-0000-4000-8000-000000000000",
      "colors": {},
      "usedAt": "2026-10-24T12:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

Answer 404:

```json
{
  "error": {
    "code": "not_found",
    "message": "There is nothing at that id for this caller.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `not_found` | Check the id. Another account's objects read as not found too. |

Every other refusal it may answer: `invalid_id` (400), `invalid_api_key` (401), `unauthenticated` (401), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

### Recolor a result

`POST /v1/conversions/{id}/variants`

`colors` maps the file's own colors to new ones, every use of each (fills, strokes, gradient stops, the ground); two to one value merge them. Keys are the result's paints and its ground (`/facts`); the mapping is stored canonical, and the same one saved again is the same variant, used now. Free; its files download under the image's unlock (`/download?variant=`).

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |
| `colors` | body | object | yes | `{"#old": "#new"}`, each `#rrggbb`. |

```bash
curl -X POST https://api.vecsnap.com/v1/conversions/00000000-0000-4000-8000-000000000000/variants \
  -H "Authorization: Bearer $VECSNAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"colors":{}}'
```

Answer 200:

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "colors": {},
  "usedAt": "2026-10-24T12:00:00.000Z"
}
```

Answer 404:

```json
{
  "error": {
    "code": "not_found",
    "message": "There is nothing at that id for this caller.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 409:

```json
{
  "error": {
    "code": "conversion_failed",
    "message": "The conversion ended without a result.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

Answer 429:

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests in a short time.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `not_found` | Check the id. Another account's objects read as not found too. |
| 409 | `conversion_failed` | Convert the image again. Converting is free. |
| 409 | `not_in_test_mode` | Use a live key for it, or test the conversion and its SVG files. |
| 409 | `not_ready` | Wait for it to finish (a webhook, the events stream, or a read), then ask again. |
| 409 | `result_outdated` | Convert the image again. |
| 429 | `rate_limited` | Wait the Retry-After seconds, then send it again. |
| 502 | `derive_failed` | Try again. The coarser picture still shows. |

Every other refusal it may answer: `invalid_colors` (400), `invalid_id` (400), `invalid_request` (400), `malformed_body` (400), `invalid_api_key` (401), `unauthenticated` (401), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `internal_error` (500), `auth_unavailable` (503).

### List palettes

`GET /v1/palettes`

The account's palettes, the last changed first: at most 20, in one page.

```bash
curl https://api.vecsnap.com/v1/palettes \
  -H "Authorization: Bearer $VECSNAP_API_KEY"
```

Answer 200:

```json
{
  "object": "list",
  "data": [
    {
      "object": "palette",
      "id": "00000000-0000-4000-8000-000000000000",
      "name": "Brand colors",
      "colors": [
        "#1f6feb"
      ],
      "updatedAt": "2026-10-24T12:00:00.000Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
```

Every refusal it may answer: `invalid_api_key` (401), `unauthenticated` (401), `account_required` (403), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

### Save a palette

`POST /v1/palettes`

A new palette; an account keeps at most 20 (409 `palette_limit`).

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `name` | body | string | yes | Trimmed. |
| `colors` | body | array of string | yes | `#rrggbb`, either case; a repeated one is kept once. |

```bash
curl -X POST https://api.vecsnap.com/v1/palettes \
  -H "Authorization: Bearer $VECSNAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Brand colors","colors":["example"]}'
```

Answer 200:

```json
{
  "object": "palette",
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "Brand colors",
  "colors": [
    "#1f6feb"
  ],
  "updatedAt": "2026-10-24T12:00:00.000Z"
}
```

Answer 409:

```json
{
  "error": {
    "code": "palette_limit",
    "message": "The account has the most palettes it can keep.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 409 | `palette_limit` | Delete a palette, then save the new one. |

Every other refusal it may answer: `invalid_palette` (400), `invalid_request` (400), `malformed_body` (400), `invalid_api_key` (401), `unauthenticated` (401), `account_required` (403), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

### Change a palette

`PATCH /v1/palettes/{id}`

Its name and colors, whole. Another's is 404 `palette_not_found`, the same as none.

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |
| `name` | body | string | yes | Trimmed. |
| `colors` | body | array of string | yes | `#rrggbb`, either case; a repeated one is kept once. |

```bash
curl -X PATCH https://api.vecsnap.com/v1/palettes/00000000-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $VECSNAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Brand colors","colors":["example"]}'
```

Answer 200:

```json
{
  "object": "palette",
  "id": "00000000-0000-4000-8000-000000000000",
  "name": "Brand colors",
  "colors": [
    "#1f6feb"
  ],
  "updatedAt": "2026-10-24T12:00:00.000Z"
}
```

Answer 404:

```json
{
  "error": {
    "code": "palette_not_found",
    "message": "There is no such palette in this account.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `palette_not_found` | Use a palette id GET /palettes listed. |

Every other refusal it may answer: `invalid_id` (400), `invalid_palette` (400), `invalid_request` (400), `malformed_body` (400), `invalid_api_key` (401), `unauthenticated` (401), `account_required` (403), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

### Delete a palette

`DELETE /v1/palettes/{id}`

Removed for good. Another's is 404 `palette_not_found`, the same as none.

| Field | In | Type | Required | What |
| --- | --- | --- | --- | --- |
| `id` | path | id | yes |  |

```bash
curl -X DELETE https://api.vecsnap.com/v1/palettes/00000000-0000-4000-8000-000000000000 \
  -H "Authorization: Bearer $VECSNAP_API_KEY"
```

Answer 200:

```json
{
  "id": "00000000-0000-4000-8000-000000000000",
  "deleted": true
}
```

Answer 404:

```json
{
  "error": {
    "code": "palette_not_found",
    "message": "There is no such palette in this account.",
    "requestId": "req_8Gq2Lw0fTz1kVb6s"
  }
}
```

| Status | Code | What to do |
| --- | --- | --- |
| 404 | `palette_not_found` | Use a palette id GET /palettes listed. |

Every other refusal it may answer: `invalid_id` (400), `invalid_api_key` (401), `unauthenticated` (401), `account_required` (403), `email_unverified` (403), `plan_required` (403), `account_exists` (409), `email_refused` (422), `rate_limited` (429), `internal_error` (500), `auth_unavailable` (503).

---
This page: https://vecsnap.com/docs/api/colors
