Product Categories

7 views Markdown

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.
statusstringactive ya da inactive.
visibilitystringvisible ya da invisible.
rankintThe display order.
icon_typestringfont 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.
langsobjectA 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 'https://panel.example.com/api/v1/admin/products/categories/18' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/products/categories/18', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$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);
$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.
statusstringactive ya da inactive.
sub_titlestringA sub-title.
contentstringThe category text.
routestringThe URL slug.
icon_typestringfont 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_required422group was empty.
route_exists422The slug is already used by another record.
create_failed422Creation was refused.
insufficient_scope403The key lacks the required scope.
Request
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}'
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();
$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);
// 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.
statusstringactive ya da inactive.
sub_titlestringA sub-title.
contentstringThe category text.
routestringThe URL slug.
icon_typestringfont 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 -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}'
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();
$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);
$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 -X DELETE 'https://panel.example.com/api/v1/admin/products/categories/18' \
  -H "Authorization: Bearer $API_KEY"
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();
$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);
$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 -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"}'
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();
$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);
// 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 -X DELETE -G 'https://panel.example.com/api/v1/admin/products/categories/18/image' \
  -H "Authorization: Bearer $API_KEY" \
  -d type=header-background
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();
$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);
$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.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.