The Services You Own
The eleven endpoints that read, set, renew and end a service you own.
Overview
A service is the thing a customer owns: a hosting package, a server, a software licence. These eleven endpoints read that record, change its settings, renew it and ask for it to end.
Renewal runs in two steps: these endpoints raise an invoice and payment happens on the invoice endpoints. Automatic renewal, meanwhile, is governed by two switches, one at the account level and one on the service.
Some products are charged by use as well. A meter can be switched, and an unpaid usage invoice locks it shut.
Reference
Listing the Services
Returns the services the account owns.
curl 'https://panel.example.com/api/v1/client/services?status=active' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch('https://panel.example.com/api/v1/client/services?status=active', {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const soon = data.filter((s) => s.due_date && s.due_date < horizon);$ch = curl_init('https://panel.example.com/api/v1/client/services?status=active');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The filter takes a BADGE GROUP rather than a raw state: pending covers two raw states at once.
$rows = Kernel::internal('client:Services/GetServices',
['owner_id' => $uid, 'status' => 'pending'])['data'];Reading a Service
Returns a service's settings, access details and where renewal stands.
curl 'https://panel.example.com/api/v1/client/services/622' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
if (data.renewal.available) showRenewButton(data.renewal.cycles);$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A DOMAIN is NOT read here: domains have endpoints of their own and answer not found on this one.
$s = Kernel::internal('client:Services/GetService', ['owner_id' => $uid, 'id' => $id])['data'];
$panel = $s['managed'];Changing the Service Preferences
Changes automatic renewal and the billing profile.
curl -X PATCH 'https://panel.example.com/api/v1/client/services/622' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"auto_renew":true,"billing_profile_id":3}'const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ auto_renew: true }),
});
if (res.status === 422) explainLock(await res.json());$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['auto_renew' => true]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The per-service switch is LOCKED while account-wide automatic payment is on; look at the wallet setting first.
$s = Kernel::internal('client:Services/GetService', ['owner_id' => $uid, 'id' => $id])['data'];
if (! $s['auto_renew_locked'])
Kernel::internal('client:Services/UpdateService',
['owner_id' => $uid, 'id' => $id, 'auto_renew' => true]);Raising a Renewal Invoice
Raises a renewal invoice for the service.
curl -X POST 'https://panel.example.com/api/v1/client/services/622/renew' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"cycle":"annually"}'const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/renew`, {
method: 'POST',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ cycle }),
});
const { data } = await res.json();
if (data.existing) note('An invoice was already open.');$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/renew');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['cycle' => $cycle]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// It RAISES an invoice and PAYS nothing: hand the id it returns to the invoice payment endpoint.
$inv = Kernel::internal('client:Services/RenewService',
['owner_id' => $uid, 'id' => $id])['data'];
Kernel::internal('client:Invoices/PayInvoice',
['owner_id' => $uid, 'id' => $inv['invoice_id'], 'payment' => ['method' => 'balance']]);Asking to Cancel
Opens a request to cancel the service.
curl -X POST 'https://panel.example.com/api/v1/client/services/622/cancellation' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"urgency":"period-ending","reason":"not-needed"}'const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/cancellation`, {
method: 'POST',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ urgency: 'period-ending', reason, note }),
});
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/cancellation');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['urgency' => 'period-ending', 'reason' => $why]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Choosing AT ONCE ends the service there and then: the period-end choice lets the time already paid run out.
Kernel::internal('client:Services/CreateCancellation',
['owner_id' => $uid, 'id' => $id, 'urgency' => 'period-ending', 'reason' => 'not-needed']);Withdrawing a Cancellation
Withdraws an open cancellation request.
curl -X DELETE 'https://panel.example.com/api/v1/client/services/622/cancellation' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/cancellation`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${clientKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/cancellation');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Once a request is APPROVED withdrawing may not save the service; read its state from the detail.
$s = Kernel::internal('client:Services/GetService', ['owner_id' => $uid, 'id' => $id])['data'];
$pending = ($s['cancellation']['status'] ?? '') === 'pending';Reading the Service Add-ons
Returns the add-ons tied to the service.
curl 'https://panel.example.com/api/v1/client/services/622/addons' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/addons`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const waiting = data.filter((a) => a.unpaid_invoice_id > 0);$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/addons');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A waiting add-on stays asleep until its INVOICE is paid; take the id from here and pay it.
$rows = Kernel::internal('client:Services/GetServiceAddons',
['owner_id' => $uid, 'id' => $id])['data'];
$due = array_filter(array_column($rows, 'unpaid_invoice_id'));Reading the Service Invoices
Returns the documents that bill this service.
curl 'https://panel.example.com/api/v1/client/services/622/invoices' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/invoices`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const open = data.filter((i) => i.state !== 'paid');$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/invoices');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// There is NO PAGING here, only a ceiling: use the invoice listing for a long history.
$rows = Kernel::internal('client:Services/GetServiceInvoices',
['owner_id' => $uid, 'id' => $id, 'limit' => 100])['data'];Reading the Meters
Returns the meters charged by use and what they are likely to cost.
curl 'https://panel.example.com/api/v1/client/services/622/metrics' \
-H "Authorization: Bearer $CLIENT_KEY"const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/metrics`, {
headers: { Authorization: `Bearer ${clientKey}` },
});
const { data } = await res.json();
const bill = data.metrics.reduce((s, m) => s + (m.estimated_charge?.amount ?? 0), 0);$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/metrics');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A meter switched off is still billed for what it USED UP TO THEN: closing it erases no history.
$m = Kernel::internal('client:Services/GetServiceMetrics',
['owner_id' => $uid, 'id' => $id])['data'];
$blocked = $m['has_unpaid'];Switching a Meter
Turns one meter on or off.
curl -X PATCH 'https://panel.example.com/api/v1/client/services/622/metrics/storage' \
-H "Authorization: Bearer $CLIENT_KEY" \
-H 'Content-Type: application/json' \
-d '{"enabled":true}'const res = await fetch(`https://panel.example.com/api/v1/client/services/${id}/metrics/${key}`, {
method: 'PATCH',
headers: {
Authorization: `Bearer ${clientKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: true }),
});
if (res.status === 422) explainLock(await res.json());$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/metrics/' . $key);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $clientKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['enabled' => true]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The one way past the lock is PAYING THE INVOICE that blocks it; its id is written on the meter.
$m = Kernel::internal('client:Services/GetServiceMetrics',
['owner_id' => $uid, 'id' => $id])['data'];
$blocker = array_column($m['metrics'], 'locked_by_invoice_id', 'key')[$key] ?? 0;The Usage Billing History
Returns the use billed period by period.
curl 'https://panel.example.com/api/v1/client/services/622/metrics/billing?metric=storage' \
-H "Authorization: Bearer $CLIENT_KEY"const url = new URL(`https://panel.example.com/api/v1/client/services/${id}/metrics/billing`);
url.searchParams.set('metric', key);
const res = await fetch(url, { headers: { Authorization: `Bearer ${clientKey}` } });
const { data } = await res.json();$ch = curl_init('https://panel.example.com/api/v1/client/services/' . $id . '/metrics/billing');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $clientKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A PENDING row is a period not yet billed: its invoice id is zero and the amount can still move.
$rows = Kernel::internal('client:Services/GetServiceMetricBilling',
['owner_id' => $uid, 'id' => $id])['data'];
$open = array_filter($rows, fn ($r) => $r['state'] === 'pending');Pitfalls
The renewal endpoint produces an invoice and stops there; the service runs longer only once that invoice is paid. A field in the answer says whether no new document was raised and an already open one came back. Hand the id to the invoice payment endpoint.
While account-wide automatic payment is on the per-service switch is locked and a change is refused. The lock field in the detail says so beforehand. The way past it is the wallet setting rather than the service one.
The urgency on a cancellation takes one of two values. At once ends the service there and then and the time already paid for does not come back, while at the end of the period lets it run out. Show the customer that difference.
Switching a meter off does not save the use up to that moment from being billed: it still enters the document at period end. An unpaid usage invoice also locks the meter shut, and paying that invoice is the only key.
A domain record looks like a service and answers not found on these endpoints, because it has endpoints of its own. Code looking for a domain in a service listing comes back empty and can read that as a fault. See the domain articles instead.
The endpoint giving a service's invoices takes a ceiling alone and no paging. Use the general invoice listing for a long history and search there. The add-on listing carries the same limit.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.