## Upload product image

`POST /v1/catalog-products/:productId/images`

Uploads a catalog product image from base64 content: a detail picture, a preview picture or a gallery image. The request body is flat JSON.

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|:--------:|-------------|
| `productId` (path) | number | yes | Product ID. List: [`GET /v1/catalog-products`](/docs/entities/catalog-products/list). The ID of an SKU from [`GET /v1/catalog-skus`](/docs/entities/catalog-skus/list), an offer from [`GET /v1/catalog-offers`](/docs/entities/catalog-offers/list) or a service from [`GET /v1/catalog-services`](/docs/entities/catalog-services/list) also works |

## Request fields (body)

| Field | Type | Required | Description |
|-------|------|:--------:|-------------|
| `type` | string | yes | Image type: `DETAIL_PICTURE` — detail picture, `PREVIEW_PICTURE` — preview picture, `MORE_PHOTO` — gallery image. Any other value is rejected before calling Bitrix24 |
| `fileName` | string | yes | File name with an extension, for example `photo.png`. An empty name is rejected |
| `fileContent` | string | yes | File content in base64 |

The body accepts no other fields: an extra field such as `description` returns `400 INVALID_PARAMS`. The request body limit is 40 MiB.

## Examples

### curl — personal key

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/catalog-products/7257/images" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "MORE_PHOTO",
    "fileName": "photo.png",
    "fileContent": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg=="
  }'
```

### curl — OAuth application

```bash
curl -X POST "https://vibecode.bitrix24.com/v1/catalog-products/7257/images" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "MORE_PHOTO",
    "fileName": "photo.png",
    "fileContent": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg=="
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/catalog-products/7257/images', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'MORE_PHOTO',
    fileName: 'photo.png',
    fileContent: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg==',
  }),
})

const { success, data } = await res.json()
console.log('Image ID:', data.id)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/catalog-products/7257/images', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    type: 'MORE_PHOTO',
    fileName: 'photo.png',
    fileContent: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL/nwAAAABJRU5ErkJggg==',
  }),
})

const { success, data } = await res.json()
```

## Response fields

A successful upload responds with HTTP status `201 Created`.

| Field | Type | Description |
|-------|------|-------------|
| `success` | boolean | Always `true` on success |
| `data` | object | Uploaded image |
| `data.id` | number | Image ID |
| `data.productId` | number | ID of the product that owns the image |
| `data.type` | string | Image type: `DETAIL_PICTURE` — detail picture, `PREVIEW_PICTURE` — preview picture, `MORE_PHOTO` — gallery image |
| `data.name` | string | File name |
| `data.createTime` | string \| null | Creation time in ISO 8601 format with a UTC offset, or `null` |
| `data.detailUrl` | string | Image address — a path starting with `/` relative to the Bitrix24 account origin, or an absolute `http`/`https` URL. Access without a Bitrix24 account session is not guaranteed. Treat it as untrusted input — see [Product images](/docs/entities/catalog-products/images) |

## Response example

```json
{
  "success": true,
  "data": {
    "id": 38993,
    "productId": 7257,
    "type": "MORE_PHOTO",
    "name": "photo.png",
    "createTime": "2026-10-08T20:59:02+00:00",
    "detailUrl": "https://cdn.bitrix24.com/b00000000/iblock/2ba/2bae996b883855a3997fde84f01c7aee/photo.png"
  }
}
```

## Error response example

400 — an unknown `type`, an empty file name, content that is not base64, or an extra body field:

```json
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMS",
    "message": "type, fileName and base64 fileContent are required"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|------|-------------|
| 400 | `INVALID_PARAMS` | The body failed validation: an unknown `type`, an empty or missing `fileName`, `fileContent` that is not base64, or another field in the body |
| 400 | `INVALID_PARAMS` | `productId` is not a canonical positive integer — for example, `abc`, `0` or a value outside the JavaScript safe integer range |
| 404 | `ENTITY_NOT_FOUND` | No product with this `productId` exists |
| 413 | `PAYLOAD_TOO_LARGE` | The request body exceeds 40 MiB |
| 422 | `BITRIX_ERROR` | Bitrix24 did not accept the file, for example a name with a non-image extension such as `note.txt` |
| 422 | `BITRIX_ERROR` | The new image was not saved: replacing `DETAIL_PICTURE` or `PREVIEW_PICTURE` left the previous picture in place. The message is `Product image was not saved` |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 is unavailable or returned a response that failed the shape and safety checks |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is invalid |
| 401 | `TOKEN_MISSING` | The key has no Bitrix24 tokens |
| 403 | `SCOPE_DENIED` | The key does not have the `catalog` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is read-only. Uploads and deletes require a write-enabled key |
| 403 | `BITRIX_ACCESS_DENIED` | Bitrix24 denied access to the catalog |
| 429 | `RATE_LIMITED` | The request limit was reached. Retry after the time in the `Retry-After` header |
| 429 | `OPERATION_TIME_LIMIT` | The Bitrix24 operation-time limit was reached. Retry after the time in the `Retry-After` header |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 did not respond in time, so the write outcome is unknown. Check the product image list before retrying |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

- **Replacing the detail and preview pictures.** A product has one detail picture and one preview picture. Uploading `DETAIL_PICTURE` or `PREVIEW_PICTURE` again replaces the previous one: the response carries a new `id`, and the previous `id` can no longer be read. `MORE_PHOTO` adds a picture to the gallery.
- **File content is not checked.** Text uploaded as `photo.png` is saved as a picture. Make sure `fileContent` is an image before uploading.
- **Identical content shares one file.** Uploading the same content again creates an image with a new `id`, but its `name` and `detailUrl` come from the earlier file, even if `fileName` carries a different name.

## See also

- [Product images](/docs/entities/catalog-products/images)
- [List product images](/docs/entities/catalog-products/images-list)
- [Get product image](/docs/entities/catalog-products/images-get)
- [Delete product image](/docs/entities/catalog-products/images-delete)
- [Catalog products](/docs/entities/catalog-products)
