Product Requirements
The eleven endpoints that manage the fields a product asks at order time, their rules, icons and categories.
Overview
A requirement is a field the product asks the client to fill at order time: a hostname, a domain, a licence key, an install script. One thing separates it from an add-on: it has no price. An add-on is an option being sold, a requirement is a question being asked.
What matters is where the answer goes. The module_co_names mapping says which field in which module the client's answer lands in. Without a mapping the answer is stored but never used when the service is provisioned.
Reference
Listing the Requirements
Returns the requirement definitions in the catalogue.
active or inactive.curl -G 'https://panel.example.com/api/v1/admin/products/requirements' \
-H "Authorization: Bearer $API_KEY" \
-d group=serverconst url = new URL('https://panel.example.com/api/v1/admin/products/requirements');
url.searchParams.set('group', 'server');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/products/requirements?' . http_build_query(['group' => 'server']);
$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()->GetProductRequirements([], ['group' => 'server']);Requirement Detail
Returns one requirement with its rules, module mapping and options in every language.
active or inactive.text, textarea, select, radio, checkbox or file.curl 'https://panel.example.com/api/v1/admin/products/requirements/42' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/requirements/42', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirements/42');
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()->GetProductRequirement(['id' => 42]);
// An empty mapping means the answer NEVER reaches the module.
$mapped = ($response['data']['module_co_names']['HetznerCloud'] ?? '') !== '';{
"data": {
"id": 42,
"group": "server",
"category": 297,
"status": "active",
"rank": 0,
"module_co_names": { "HetznerCloud": "user_data" },
"type": "textarea",
"properties": { "compulsory": false },
"langs": {
"en": {
"name": "User Data",
"description": "Cloud-init data run at first boot.",
"options": []
}
}
}
}Creating a Requirement
Opens a new requirement definition in the catalogue.
text, textarea, select, radio, checkbox or file. Defaults to text.active or inactive.{name, mkey} object. Choice types only; sending it replaces the whole set.group was empty.category was empty.curl -X POST 'https://panel.example.com/api/v1/admin/products/requirements' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"group":"server","category":4,"type":"text","name":{"en":"Server Hostname"},"compulsory":true}'const res = await fetch('https://panel.example.com/api/v1/admin/products/requirements', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
group: 'server',
category: 4,
type: 'text',
name: { en: 'Server Hostname' },
compulsory: true,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirements');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'group' => 'server',
'category' => 4,
'type' => 'text',
'name' => ['en' => 'Server Hostname'],
'compulsory' => true,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Give the mapping too, or the answer lands nowhere in the module.
$response = Api::Products()->CreateProductRequirement([
'group' => 'server',
'category' => 4,
'type' => 'text',
'name' => ['en' => 'Server Hostname'],
'module_co_names' => ['HetznerCloud' => 'hostname'],
]);Updating a Requirement
Applies the fields you send and leaves the rest as they were. If you send the options, the set is replaced as a whole.
text, textarea, select, radio, checkbox or file. Leave it out and the stored type stays.active or inactive.{name, mkey} object. Choice types only; sending it replaces the whole set.curl -X PATCH 'https://panel.example.com/api/v1/admin/products/requirements/7' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":"inactive","compulsory":false}'const res = await fetch('https://panel.example.com/api/v1/admin/products/requirements/7', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: 'inactive', compulsory: false }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirements/7');
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',
'compulsory' => false,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Even to change one option, send the WHOLE set.
$req = Api::Products()->GetProductRequirement(['id' => 7])['data'];
$options = $req['langs']['en']['options'];
// ... change $options ...
Api::Products()->UpdateProductRequirement([
'id' => 7,
'options' => $options,
]);Deleting a Requirement
Deletes the requirement definition. Its language records go too.
gate:product.requirement_delete hook vetoed the operation.curl -X DELETE 'https://panel.example.com/api/v1/admin/products/requirements/7' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/requirements/7', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirements/7');
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()->DeleteProductRequirement(['id' => 7]);Uploading a Requirement Icon
Uploads the requirement icon. Image files and SVG are accepted.
curl -X POST 'https://panel.example.com/api/v1/admin/products/requirements/7/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/requirements/7/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/requirements/7/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()->UploadRequirementIcon([
'id' => 7,
'image' => 'data:image/svg+xml;base64,' . base64_encode($svg),
]);Deleting a Requirement Icon
Removes the uploaded icon image.
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/requirements/7/icon' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/requirements/7/icon', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirements/7/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()->DeleteProductRequirementIcon(['id' => 7]);Listing the Categories
Returns the requirement categories.
curl 'https://panel.example.com/api/v1/admin/products/requirement-categories' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/requirement-categories', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirement-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()->GetRequirementCategories();Creating a Category
Opens a requirement category.
title was empty.gate:product.category_save hook vetoed the operation.curl -X POST 'https://panel.example.com/api/v1/admin/products/requirement-categories' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"title":"Server Info","rank":1}'const res = await fetch('https://panel.example.com/api/v1/admin/products/requirement-categories', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'Server Info', rank: 1 }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirement-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' => 'Server Info', 'rank' => 1]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$category = Api::Products()->CreateRequirementCategory(['title' => 'Server Info']);
Api::Products()->CreateProductRequirement([
'group' => 'server',
'category' => $category['data']['id'],
'name' => ['en' => 'Server Hostname'],
]);Updating a Category
Changes the category's title, parent or order.
curl -X PATCH 'https://panel.example.com/api/v1/admin/products/requirement-categories/4' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"title":"Server Details"}'const res = await fetch('https://panel.example.com/api/v1/admin/products/requirement-categories/4', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ title: 'Server Details' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirement-categories/4');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['title' => 'Server Details']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->UpdateRequirementCategory([
'id' => 4,
'title' => 'Server Details',
]);Deleting a Category
Deletes the requirement category.
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/requirement-categories/4' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/requirement-categories/4', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/requirement-categories/4');
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()->DeleteRequirementCategory(['id' => 4]);Pitfalls
Asking the client is not enough. With no field name for that module in module_co_names, the answer sits in the record and is never used at provisioning. No error is raised either: the server comes up with the wrong name and nothing says why. Give the mapping when you open the requirement.
If options is in the update body, every existing option is dropped in every language and replaced by the set you sent. Leaving the field out keeps them.
The maximum file size and the allowed extensions mean something only on the file type, and options only on the choice types. A rule sent to the wrong type raises no error; it is stored quietly and never applied.
An order cannot go through while a requirement marked compulsory is left empty. Adding a compulsory requirement to a product that is already selling stops purchases of it there and then, so make sure the question can actually be answered on the storefront first.
Related Articles
Vielen Dank für Ihre Rückmeldung!
Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.