What Is on Sale
The three endpoints giving the products on sale, their categories and the order schema.
Overview
These three endpoints show what a customer can buy. What the shop window holds is what appears here: a product that is closed, hidden or in a shut group never shows at all.
The prices come in the account's wallet currency. The figure listed can be compared with the balance straight away, and ordering brings no surprise.
The detail endpoint gives more than a catalogue record: it is the schema of the order body. The cycles, add-ons, questions and the domain axis are read from it.
Reference
Listing the Products
Returns the products that can be ordered, in shop-window order.
curl 'https://panel.example.com/api/v1/client/products?type=hosting' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch('https://panel.example.com/api/v1/client/products?type=hosting', {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data, meta } = await res.json();
renderPrices(data, meta.currency);$ch = curl_init('https://panel.example.com/api/v1/client/products?type=hosting');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The prices come in the WALLET currency: compare them with the balance straight and convert nothing.
$r = Kernel::internal('client:Products/GetProducts', ['owner_id' => $uid, 'type' => 'hosting']);
$cur = $r['meta']['currency'];Listing the Categories
Returns the product categories on show as one flat list.
curl 'https://panel.example.com/api/v1/client/products/categories' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch('https://panel.example.com/api/v1/client/products/categories', {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const roots = data.filter((c) => c.parent === null);$ch = curl_init('https://panel.example.com/api/v1/client/products/categories');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The list is FLAT: build the tree from the parent field yourself, since the endpoint gives none.
$rows = Kernel::internal('client:Products/GetProductCategories', ['owner_id' => $uid])['data'];
$tree = [];
foreach ($rows as $c) $tree[$c['parent'] ?? 0][] = $c;The Product and Its Order Schema
Returns a product with everything needed to build an order body.
curl 'https://panel.example.com/api/v1/client/products/36' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/products/${id}`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const must = data.requirements.filter((r) => r.required);
const cycles = data.pricing.map((p) => p.cycle);$ch = curl_init('https://panel.example.com/api/v1/client/products/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A NEW domain cannot be ordered here: registering and transferring happen on the Domains endpoints.
$pr = Kernel::internal('client:Products/GetProduct', ['owner_id' => $uid, 'id' => $id])['data'];
$viaDomains = ($pr['domain']['register_transfer'] ?? '') === 'via_domains_endpoints';Pitfalls
The domain axis in the product detail covers using a domain you already own or picking a free subdomain. Registering a new domain or transferring one belongs to the domain endpoints. Mixing them up sends you looking for an order field that does not exist.
Where a cycle carries a promotion the price field holds the promotional amount and the promotion flag comes back true. Applying a discount on top of the listing takes it down twice. This is also what the order engine charges, so the listing and the basket never disagree.
The detail endpoint meets an unknown, closed, hidden and group-shut product with the same answer. That is deliberate: the answer never leaks whether the product exists. When one is missing from the listing the API will not say why, and the operator has to be asked.
The quantity rule sits in one of three modes: one unit per order, a separate service for each unit, or a quantity scaling inside one service. Ordering ten in the second mode brings ten services into being, and in the third one service at ten times the price. Read the mode before building the interface.
The categories come back flat and the hierarchy is built from the parent field. There is no paging either: the whole set comes every time. A chosen category goes into the product listing as a filter and does not take its children in by itself.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.