# Product Media

https://dev.wisecp.com/es/product-media

The eleven endpoints that manage a product's gallery, order image and delivery file.

## Overview

A product has three separate visual assets and they should not be confused. The **gallery** holds several images and can be ordered; the **order image** is a single one shown on the order screen; the **delivery file** is not an image at all, it is what a software product gives the client.

None of the upload endpoints expect a multipart form. You send the file as a base64 data URI or hand over a link that can be fetched, and the server pulls it itself.

## Reference

### Listing the Gallery

get/api/v1/admin/products/{id}/images

`Products/GetProductImages` admin ordered

Returns the product's gallery images in their order.

Response fields data[] — 7

idintImage id.

typestringThe image role: `photo`, `header-background`, `cover` or `mockup`.

sizestringA human-readable file size.

namestringThe stored file name.

urlstringThe public address.

titlestringThe image title.

sort_orderintIts place in the gallery.

Errors 2

not_found404No such product.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/products/15/images' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/products/15/images', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->GetProductImages(['id' => 15]);
```

Response 200

```json
{
  "data": [
    {
      "id": 90,
      "type": "photo",
      "size": "120 KB",
      "name": "ab12.jpg",
      "url": "https://example.com/uploads/ab12.jpg",
      "title": "Screenshot",
      "sort_order": 1
    }
  ]
}
```

### Uploading to the Gallery

post/api/v1/admin/products/{id}/images

`Products/UploadProductImage` admin 201

Adds an image to the gallery. You send the file as base64 or hand over an address.

Body 3

imagestringrequiredThe image. A base64 data URI or a link that can be fetched.

titlestringThe image title.

typestringThe image role: `photo`, `header-background`, `cover` or `mockup`. Defaults to `photo`.

Response fields data — 5

idintId of the new image.

typestringThe image role it was stored under.

titlestringThe image title.

sort_orderintIts place in the gallery.

urlstringThe public address.

Errors 5

not_found404No such product.

file_required422The file field was empty.

file_invalid422The file could not be read or its type was refused.

file_failed422The file could not be stored.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/products/15/images' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"image":"data:image/png;base64,iVBORw0KGgo...","title":"Screenshot"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/images', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    image: 'data:image/png;base64,iVBORw0KGgo...',
    title: 'Screenshot',
    type: 'photo',
  }),
});

const body = await res.json();
```

```php
$data = base64_encode(file_get_contents('screenshot.png'));

$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'image' => 'data:image/png;base64,' . $data,
        'title' => 'Screenshot',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// A new image lands at the END of the gallery; ordering is a separate call.
$image = Api::Products()->UploadProductImage([
    'id'    => 15,
    'image' => 'data:image/png;base64,' . base64_encode($bytes),
    'title' => 'Screenshot',
]);
```

### Updating an Image

patch/api/v1/admin/products/{id}/images/{image_id}

`Products/UpdateProductImage` admin type moves the file

Changes an image's title or role. Changing the role moves the file between folders.

Body 2

titlestringThe new title. If you send it, it cannot be empty.

typestringThe new role: `photo`, `header-background`, `cover` or `mockup`.

Response fields data — 2

updatedboolWhether the update succeeded.

idintImage id.

Errors 4

not_found404No such product or image.

title_required422A title was sent but it was empty.

invalid_type422The role is not one of the allowed values.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PATCH 'https://panel.example.com/api/v1/admin/products/15/images/91' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"title":"Dashboard","type":"cover"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/images/91', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ title: 'Dashboard', type: 'cover' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images/91');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'title' => 'Dashboard',
        'type'  => 'cover',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Changing the role moves the file: the old address stops working.
$response = Api::Products()->UpdateProductImage([
    'id'       => 15,
    'image_id' => 91,
    'type'     => 'cover',
]);
```

### Deleting an Image

delete/api/v1/admin/products/{id}/images/{image_id}

`Products/DeleteProductImage` admin

Deletes a single image from the gallery.

Response fields data — 2

deletedboolWhether the delete succeeded.

idintId of the deleted image.

Errors 2

not_found404No such product or image.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/15/images/91' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/images/91', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images/91');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->DeleteProductImage([
    'id'       => 15,
    'image_id' => 91,
]);
```

### Clearing the Gallery

delete/api/v1/admin/products/{id}/images

`Products/ClearProductImages` admin cannot be undone

Deletes every gallery image the product has.

Response fields data — 1

clearedintHow many images were deleted.

Errors 2

not_found404No such product.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/15/images' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/images', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The address is the SAME as deleting one image; only the image id is missing.
$response = Api::Products()->ClearProductImages(['id' => 15]);
```

### Ordering the Gallery

put/api/v1/admin/products/{id}/images/order

`Products/ReorderProductImages` admin full list

Sets the gallery order from the list of ids you give.

Body 1

image_idsint[]requiredThe image ids, in the order you want.

Response fields data[] — 7

idintImage id.

typestringThe image role: `photo`, `header-background`, `cover` or `mockup`.

sizestringA human-readable file size.

namestringThe stored file name.

urlstringThe public address.

titlestringThe image title.

sort_orderintIts place in the gallery.

Errors 3

not_found404No such product.

ids_required422`image_ids` was empty.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/products/15/images/order' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"image_ids":[92,90,91]}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/images/order', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ image_ids: [92, 90, 91] }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/images/order');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['image_ids' => [92, 90, 91]]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Even to move one image to the front, send the WHOLE list.
$ids = array_column(Api::Products()->GetProductImages(['id' => 15])['data'], 'id');
array_unshift($ids, array_pop($ids));

$response = Api::Products()->ReorderProductImages([
    'id'        => 15,
    'image_ids' => $ids,
]);
```

### Reading the Delivery File

get/api/v1/admin/products/{id}/delivery-file

`Products/GetProductDeliveryFile` admin software products

Returns the file a software product hands to the client.

Response fields data — 2

namestringThe stored file name.

sizestringA human-readable file size.

Errors 2

not_found404No such product.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/products/15/delivery-file' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch('https://panel.example.com/api/v1/admin/products/15/delivery-file', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();

// With no file uploaded, data comes back null.
if (body.data === null) return;
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/delivery-file');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->GetProductDeliveryFile(['id' => 15]);

$file = $response['data'] ?? null;
```

### Uploading the Delivery File

put/api/v1/admin/products/{id}/delivery-file

`Products/UploadProductDeliveryFile` admin extension limits

Uploads the delivery file. The previous one is removed; a product carries one at a time.

Body 1

filestringrequiredThe file. A base64 data URI or a link that can be fetched. Executable page extensions are refused.

Response fields data — 2

namestringThe stored file name.

sizestringA human-readable file size.

Errors 5

not_found404No such product.

file_required422The file field was empty.

file_invalid422The file could not be read or its type was refused.

file_failed422The file could not be stored.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/products/15/delivery-file' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"file":"https://cdn.example.com/setup-v2.zip"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/delivery-file', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ file: 'https://cdn.example.com/setup-v2.zip' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/delivery-file');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'file' => 'https://cdn.example.com/setup-v2.zip',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Uploading a new version removes the old one - you may want a copy first.
$response = Api::Products()->UploadProductDeliveryFile([
    'id'   => 15,
    'file' => 'https://cdn.example.com/setup-v2.zip',
]);
```

### Deleting the Delivery File

delete/api/v1/admin/products/{id}/delivery-file

`Products/DeleteProductDeliveryFile` admin

Removes the delivery file.

Response fields data — 2

deletedboolWhether the delete succeeded.

idintProduct id.

Errors 2

not_found404No such product.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/15/delivery-file' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/delivery-file', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/delivery-file');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->DeleteProductDeliveryFile(['id' => 15]);
```

### Setting the Order Image

put/api/v1/admin/products/{id}/order-image

`Products/SetProductOrderImage` admin a single image

Sets the image shown on the order screen. A product carries one of these.

Body 1

imagestringrequiredThe image. A base64 data URI or a link that can be fetched.

Response fields data — 1

urlstringThe image's public address.

Errors 5

not_found404No such product.

file_required422The file field was empty.

file_invalid422The file could not be read or its type was refused.

file_failed422The file could not be stored.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/products/15/order-image' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"image":"https://cdn.example.com/plan.png"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/order-image', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ image: 'https://cdn.example.com/plan.png' }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/order-image');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'image' => 'https://cdn.example.com/plan.png',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->SetProductOrderImage([
    'id'    => 15,
    'image' => 'https://cdn.example.com/plan.png',
]);
```

### Deleting the Order Image

delete/api/v1/admin/products/{id}/order-image

`Products/DeleteProductOrderImage` admin

Removes the order image.

Response fields data — 2

deletedboolWhether the delete succeeded.

idintProduct id.

Errors 2

not_found404No such product.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/15/order-image' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/15/order-image', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/15/order-image');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
$response = Api::Products()->DeleteProductOrderImage(['id' => 15]);
```

## Pitfalls

> **One address deletes one image or the whole gallery**
> 
> Clearing the gallery and deleting one image use the **same address**; the only difference is the image id at the end. Code that builds that id from an empty variable wipes the entire gallery instead of the one image it meant to remove.

> **Ordering takes the full list**
> 
> The ordering endpoint takes the list you give as the order, and images missing from it end up in an undefined place. Even to move one image to the front you read the gallery first and send every id. A newly uploaded image always lands at the end.

> **Changing the role moves the file**
> 
> Changing an image's role moves the file between folders, so the **old address stops working**. If you cached that address on your side, read it back after the update.

> **The delivery file is single and extension-limited**
> 
> Uploading a new delivery file **removes** the previous one; a product cannot hold two versions at once. Executable page extensions are also refused, so there is no way to hand the client a file that would run on the server.

## Related Articles

- [Product Endpoints](https://dev.wisecp.com/en/product-endpoints)
- [Product Categories](https://dev.wisecp.com/en/product-categories)
