# Product Categories

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

The six endpoints that read, open, edit and delete product categories inside a group and manage their images.

## Overview

A category is the container that gathers products inside a group. The **flat list** of categories comes from the lookup in the product endpoints article. The six here read, open, edit and delete one category, and manage its images.

Categories and special groups live in the same table and return the **same schema**. Read `is_category` to see which one you have.

## Reference

### Category Detail

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

`Products/GetProductCategory` admin

Returns one category with all of its settings and language content.

Response fields data — 18

idintCategory id.

kindstringThe group type it belongs to.

kind_idintSpecial group id. 0 in a fixed group.

parent_idintId of the parent category. 0 means top level.

is_categoryboolWhether this row is a category or a top-level group.

group_keystring | nullThe group key on top-level groups.

statusstring`active` ya da `inactive`.

visibilitystring`visible` ya da `invisible`.

rankintThe display order.

icon_typestring`font` ya da `image`.

iconstringThe icon class or the name of the uploaded image.

colorstringThe category colour.

list_templateintId of the list template.

upgradingboolWhether upgrading is allowed.

seo_indexboolWhether search engines may index it.

enabled_payment_gatewaysarrayRestricts payment to these gateways.

disabled_payment_gatewaysarraySwitches these gateways off.

langsobject 7 fieldsA map from language code to a content object.

titlestringThe category title.

routestringThe URL slug. Derived from the title when you leave it out.

sub_titlestringA sub-title.

contentstringThe category text.

seo_titlestringThe search title.

seo_keywordsstringThe search keywords.

seo_descriptionstringThe search description.

Errors 2

not_found404No such category.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

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

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/categories/18');
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()->GetProductCategory(['id' => 18]);

// is_category tells you whether the row is a category or a top-level group.
$isCategory = $response['data']['is_category'];
```

### Creating a Category

post/api/v1/admin/products/categories

`Products/CreateProductCategory` admin 201

Opens a category inside a group. A single field decides which group it lands in.

Body 12

groupstringrequiredThe group context: `hosting`, `server`, `software`, or the id-suffixed key for a special group. It comes from the groups lookup.

titleobjectrequiredA map from language code to title. The current language needs one.

parent_idintId of the parent category.

rankintThe display order.

statusstring`active` ya da `inactive`.

sub_titlestringA sub-title.

contentstringThe category text.

routestringThe URL slug.

icon_typestring`font` ya da `image`.

iconstringThe icon class.

colorstringThe category colour.

seo_titlestringThe search title; keywords and description are separate fields.

Response fields data

dataobjectThe category created, returned with `201`. The same schema as the category detail above.

Errors 5

title_required422There is no title in the current language.

group_required422`group` was empty.

route_exists422The slug is already used by another record.

create_failed422Creation was refused.

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/categories' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"group":"hosting","title":{"en":"Reseller Hosting"},"rank":2}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/categories', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    group: 'hosting',
    title: { en: 'Reseller Hosting' },
    rank: 2,
  }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/categories');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'group' => 'hosting',
        'title' => ['en' => 'Reseller Hosting'],
        'rank'  => 2,
    ]),
]);

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

```php
// For a special group the key carries the id as a suffix; read it from the groups lookup.
$response = Api::Products()->CreateProductCategory([
    'group' => 'special-5',
    'title' => ['en' => 'Wildcard Certificates'],
]);
```

### Updating a Category

patch/api/v1/admin/products/categories/{id}

`Products/UpdateProductCategory` admin partial-safe

Applies the fields you send. The group a category belongs to cannot be changed here.

Body 11

titleobjectA map from language code to title.

parent_idintId of the parent category.

rankintThe display order.

statusstring`active` ya da `inactive`.

sub_titlestringA sub-title.

contentstringThe category text.

routestringThe URL slug.

icon_typestring`font` ya da `image`.

iconstringThe icon class.

colorstringThe category colour.

seo_titlestringThe search title; keywords and description are separate fields.

Response fields data

dataobjectThe category as it now stands, returned with `200`. The same schema as the category detail above.

Errors 4

not_found404No such category.

route_exists422The slug is already used by another record.

update_failed422The update was refused.

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/categories/18' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"inactive","rank":5}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/products/categories/18', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 'inactive', rank: 5 }),
});

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/categories/18');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 'inactive', 'rank' => 5]),
]);

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

```php
$response = Api::Products()->UpdateProductCategory([
    'id'     => 18,
    'status' => 'inactive',
    'rank'   => 5,
]);
```

### Deleting a Category

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

`Products/DeleteProductCategory` admin cannot be undone

Deletes the category.

Response fields data — 2

deletedboolWhether the delete succeeded.

idintId of the deleted category.

Errors 3

not_found404No such category.

blocked_by_gate422The `gate:product.group_delete` hook vetoed the operation.

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/categories/18' \
  -H "Authorization: Bearer $API_KEY"
```

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

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

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/products/categories/18');
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()->DeleteProductCategory(['id' => 18]);
```

### Uploading a Category Image

post/api/v1/admin/products/categories/{id}/image

`Products/UploadCategoryImage` admin two slots

Uploads the category's icon or its header background.

Body 2

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

typestringWhich slot to fill: `icon` or `header-background`. Defaults to `icon`.

Response fields data — 2

typestringThe slot that was written: `icon` or `header-background`.

urlstringPublic URL of the stored image. The file name is randomised.

——A slot holds one image: a new upload replaces the previous one and deletes its file.

Errors 5

not_found404No such category.

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/categories/18/image' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"image":"https://cdn.example.com/icon.png","type":"icon"}'
```

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

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

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

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

```php
// Uploading an icon image also sets the icon type to image; the font icon no longer applies.
$response = Api::Products()->UploadCategoryImage([
    'id'    => 18,
    'image' => 'https://cdn.example.com/icon.png',
    'type'  => 'icon',
]);
```

### Deleting a Category Image

delete/api/v1/admin/products/categories/{id}/image

`Products/DeleteProductCategoryImage` admin

Removes the category's icon or its header background.

Query parameters 1

typestringWhich slot to empty: `icon` or `header-background`. Defaults to `icon`.

Response fields data — 3

deletedboolWhether the delete succeeded.

idintCategory id.

typestringThe slot that was emptied.

Errors 2

not_found404No such category.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE -G 'https://panel.example.com/api/v1/admin/products/categories/18/image' \
  -H "Authorization: Bearer $API_KEY" \
  -d type=header-background
```

```javascript
const url = new URL('https://panel.example.com/api/v1/admin/products/categories/18/image');
url.searchParams.set('type', 'header-background');

const res = await fetch(url, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

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

```php
$url = 'https://panel.example.com/api/v1/admin/products/categories/18/image?' . http_build_query(['type' => 'header-background']);

$ch = curl_init($url);
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()->DeleteProductCategoryImage(['id' => 18], [
    'type' => 'header-background',
]);
```

## Pitfalls

> **The group context is given only when creating**
> 
> The `group` field sets which group a category belongs to, and it works at creation only. It **cannot be changed later**: the update body has no such field. Moving a category to another group means opening a new one and deleting the old.

> **The slug is unique across records**
> 
> A clashing slug is refused on both create and update. The clash is looked for across records, not among sibling categories alone. A same-named category in another group stops you too, so a slug built from a title can collide where you did not expect it.

> **An uploaded icon overrides the font icon**
> 
> Uploading into the icon slot switches the icon type to image. The font icon you set before is stored but not shown. Going back to it means deleting the image and writing the icon type again.

## Related Articles

- [Product Endpoints](https://dev.wisecp.com/en/product-endpoints)
- [Special Groups](https://dev.wisecp.com/en/special-groups)
