Add-on Definitions
The eleven endpoints that manage add-on definitions, their options, icons and categories.
Overview
An add-on definition is the template for something sold alongside a product: extra disk, automated backups, an extra licence. An add-on already attached to a client's service is a different thing; here you manage the definition in the catalogue.
A definition holds options, and that is where the price lives. Options sit under a language because their names are translated, while the price is kept per currency.
The last four endpoints run the add-on categories. A category does nothing on its own; it organises the add-on catalogue.
Reference
Listing the Add-on Definitions
Returns the add-on definitions in the catalogue.
active or inactive.font or image.curl -G 'https://panel.example.com/api/v1/admin/products/addons' \
-H "Authorization: Bearer $API_KEY" \
-d group=hostingconst url = new URL('https://panel.example.com/api/v1/admin/products/addons');
url.searchParams.set('group', 'hosting');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/products/addons?' . http_build_query(['group' => 'hosting']);
$ch = curl_init($url);
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()->GetProductAddons([], ['group' => 'hosting']);Add-on Detail
Returns one add-on definition with its settings, rules and options in every language.
active or inactive.{id, type}.font or image.select, quantity, checkbox or radio.day, month or year.{enabled, amount} object. Present in the multi-currency shape.curl 'https://panel.example.com/api/v1/admin/products/addons/148' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/addons/148', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons/148');
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()->GetProductAddon(['id' => 148]);
// A price arrives in one of two shapes; handle both.
$option = $response['data']['langs']['en']['options'][0];
$price = $option['pricing']['1']['amount'] ?? $option['amount'] ?? 0;{
"data": {
"id": 148,
"group": "server",
"category": 296,
"status": "active",
"type": "select",
"properties": { "show_by_pp": 1, "multiple_purchases": 0 },
"requirement_ids": [],
"product_link": { "id": 0, "type": "" },
"langs": {
"en": {
"name": "Automated Backups",
"description": "Keeps daily backups.",
"options": [
{
"id": 0,
"name": "I want",
"period": "month",
"period_time": 1,
"amount": 2,
"cid": 5,
"module": {
"HetznerCloud": { "configurable": { "backup": 1 } }
}
}
]
}
}
}
}Creating an Add-on Definition
Opens a new add-on definition in the catalogue.
active or inactive.select, checkbox, radio or quantity.font or image.201. Same shape as the detail endpoint.group was empty.category was empty.curl -X POST 'https://panel.example.com/api/v1/admin/products/addons' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"group":"hosting","category":12,"type":"select","name":{"en":"Extra Disk"},"options":{"o1":{"name":{"en":"10 GB"},"pricing":{"1":{"enabled":true,"amount":5}},"cycle":"monthly"}}}'const res = await fetch('https://panel.example.com/api/v1/admin/products/addons', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
group: 'hosting',
category: 12,
type: 'select',
name: { en: 'Extra Disk' },
options: {
o1: {
name: { en: '10 GB' },
pricing: { 1: { enabled: true, amount: 5 } },
cycle: 'monthly',
},
},
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons');
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',
'category' => 12,
'type' => 'select',
'name' => ['en' => 'Extra Disk'],
'options' => [
'o1' => [
'name' => ['en' => '10 GB'],
'pricing' => [1 => ['enabled' => true, 'amount' => 5]],
'cycle' => 'monthly',
],
],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->CreateProductAddon([
'group' => 'hosting',
'category' => 12,
'type' => 'select',
'name' => ['en' => 'Extra Disk'],
'options' => [
'o1' => [
'name' => ['en' => '10 GB'],
'pricing' => [1 => ['enabled' => true, 'amount' => 5]],
'cycle' => 'monthly',
],
],
]);Updating an Add-on Definition
Applies the fields you send. If you send the options, the set is replaced as a whole. Every body field is optional.
active or inactive.select, checkbox, radio or quantity.font or image.curl -X PATCH 'https://panel.example.com/api/v1/admin/products/addons/131' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":"inactive"}'const res = await fetch('https://panel.example.com/api/v1/admin/products/addons/131', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: 'inactive' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons/131');
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']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Even to change one option's price, send the WHOLE set.
$addon = Api::Products()->GetProductAddon(['id' => 131])['data'];
$options = $addon['langs']['en']['options'];
// ... change $options ...
Api::Products()->UpdateProductAddon([
'id' => 131,
'options' => $options,
]);Deleting an Add-on Definition
Deletes the add-on definition. Its language records go too.
gate:product.addon_delete hook vetoed the operation.curl -X DELETE 'https://panel.example.com/api/v1/admin/products/addons/131' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/addons/131', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons/131');
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()->DeleteProductAddon(['id' => 131]);Uploading an Add-on Icon
Uploads the add-on icon. Image files and SVG are accepted.
curl -X POST 'https://panel.example.com/api/v1/admin/products/addons/131/icon' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"image":"data:image/png;base64,iVBORw0KGgo..."}'const res = await fetch('https://panel.example.com/api/v1/admin/products/addons/131/icon', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: 'data:image/png;base64,iVBORw0KGgo...' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons/131/icon');
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/svg+xml;base64,' . base64_encode($svg),
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->UploadAddonIcon([
'id' => 131,
'image' => 'data:image/svg+xml;base64,' . base64_encode($svg),
]);Deleting an Add-on Icon
Removes the uploaded icon image.
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/addons/131/icon' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/addons/131/icon', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addons/131/icon');
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()->DeleteProductAddonIcon(['id' => 131]);Listing the Categories
Returns the add-on categories.
curl 'https://panel.example.com/api/v1/admin/products/addon-categories' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/addon-categories', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addon-categories');
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()->GetAddonCategories();Creating a Category
Opens an add-on category.
title was empty.gate:product.category_save hook vetoed the operation.curl -X POST 'https://panel.example.com/api/v1/admin/products/addon-categories' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"title":"Resources","rank":1}'const res = await fetch('https://panel.example.com/api/v1/admin/products/addon-categories', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'Resources', rank: 1 }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addon-categories');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['title' => 'Resources', 'rank' => 1]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$category = Api::Products()->CreateAddonCategory(['title' => 'Resources']);
Api::Products()->CreateProductAddon([
'group' => 'hosting',
'category' => $category['data']['id'],
'name' => ['en' => 'Extra Disk'],
]);Updating a Category
Changes the category's title, parent or order.
parent_id.curl -X PATCH 'https://panel.example.com/api/v1/admin/products/addon-categories/12' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"title":"System Resources","rank":2}'const res = await fetch('https://panel.example.com/api/v1/admin/products/addon-categories/12', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'System Resources', rank: 2 }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addon-categories/12');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['title' => 'System Resources', 'rank' => 2]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// An empty body comes back as 'no_changes' - skip the call when nothing changed.
$response = Api::Products()->UpdateAddonCategory([
'id' => 12,
'title' => 'System Resources',
]);Deleting a Category
Deletes the add-on category.
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/addon-categories/12' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/addon-categories/12', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/addon-categories/12');
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()->DeleteAddonCategory(['id' => 12]);Pitfalls
If options is in the update body, every existing option is dropped in every language and replaced by the set you sent. To change one option's price, read the detail first and send the set back whole. Leaving the field out keeps the options.
An option carries either a flat amount with a currency id, or a map of {enabled, amount} objects per currency. Which one you get depends on how the record was written, so reading code has to handle both.
Options live inside the language object, so each language holds its own copy. Adding an option to one language and not another leaves it invisible to clients using the other one.
The category update refuses a body carrying no editable field and returns no_changes. When nothing changed, do not send the request at all.
Related Articles
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.