Service Endpoints

8 views Markdown

The four endpoints that list, read, edit and delete client services.

Overview

A service is the thing a client bought and keeps: a hosting account, a domain, a server, a software licence. These four endpoints read, edit and delete the service itself.

Responses come back raw: statuses and cycles as codes, dates in a standard shape, amounts as numbers without a symbol. Labels for humans come from the reference endpoints.

The capabilities block on the detail says which operations this service accepts. The values are derived from the module, so they are not fixed.

Reference

Listing the Services

get/api/v1/admin/services
Services/GetServices admin paged

Returns the client services, with filters.

Query parameters 9
searchstringSearches the service name, the client, the e-mail and the address.
statusstringDuruma göre süzer: waiting, inprocess, active, suspended, expired, cancelled or completed.
typestringFilters by type: domain, hosting, server, software, sms, ssl or special. On special groups the id is appended to the type.
client_idintFilters by the owning client.
product_idintFilters by product.
server_idintFilters by server. Zero gives the services with no server.
cyclestringFilters by billing cycle.
pageintDefaults to 1.
limitintDefaults to 25, maximum 100.
Response fields data[] — 16
idintService id.
namestringThe service name.
typestringThe service type.
type_idintThe sub-type id on special groups.
product_idintId of the product behind it.
domainstring | nullThe domain tied to the service.
statusstringThe service status.
amountfloatThe per-period amount. A raw number; formatting is yours.
currency_idintCurrency id of the amount.
cyclestringThe billing cycle.
qtyintThe quantity.
modulestring | nullThe module running the service.
clientobjectWho owns the service.
idintClient id.
full_namestringFirst and last name.
company_namestringCompany name.
emailstringE-mail address.
created_atdatetimeWhen the service was opened.
due_atdatetimeWhen the term ends.
renewal_atdatetimeThe renewal date.
Pagination 4
totalintTotal records matching the filter.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero means you are on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/services' \
  -H "Authorization: Bearer $API_KEY" \
  -d status=active \
  -d type=hosting
const url = new URL('https://panel.example.com/api/v1/admin/services');
url.searchParams.set('status', 'active');
url.searchParams.set('type', 'hosting');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/services?' . http_build_query(['status' => 'active', 'type' => '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);
// The pagination fields sit at the ROOT, not under 'meta'.
$page = 1;
$all  = [];

do {
    $response = Api::Services()->GetServices([], [
        'status' => 'active',
        'page'   => $page,
        'limit'  => 100,
    ]);

    $all  = array_merge($all, $response['data']);
    $page = $response['next_page'];
} while ($page > 0);
Response
{
  "data": [
    {
      "id": 510,
      "name": "Mail Hosting",
      "type": "hosting",
      "type_id": 0,
      "product_id": 15,
      "domain": "example.com",
      "status": "active",
      "amount": 10.0,
      "currency_id": 1,
      "cycle": "monthly",
      "qty": 1,
      "module": "Mailcow",
      "client": {
        "id": 50,
        "full_name": "John Doe",
        "company_name": "",
        "email": "[email protected]"
      },
      "created_at": "2026-06-18 12:00:00",
      "due_at": "2026-07-18 12:00:00",
      "renewal_at": "2026-07-18 12:00:00"
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 25,
  "next_page": 2
}

Service Detail

get/api/v1/admin/services/{id}
Services/GetService admin capabilities included

Returns a service in full, with its relations and what can be done to it.

Response fields data — 26
idintService id.
namestringThe service name.
typestringThe service type.
type_idintThe sub-type id on special groups.
product_idintId of the product behind it.
order_idintId of the order that produced it. Zero when there is none.
invoice_idintId of the first invoice.
statusstringThe service status.
amountfloatThe per-period amount.
total_amountfloatThe amount multiplied by the quantity.
currency_idintCurrency id.
qtyintThe quantity.
periodstringThe period unit.
period_timeintThe period multiplier.
cyclestringThe billing cycle.
is_overdueboolWhether it is overdue.
created_atdatetimeWhen the service was opened.
due_atdatetimeWhen the term ends.
renewal_atdatetimeThe renewal date.
modulestring | nullThe module running the service.
clientobjectWho owns the service.
idintClient id.
full_namestringFirst and last name.
company_namestringCompany name.
emailstringE-mail address.
productobjectA summary of the product: id, title, type and module.
serverobject | nullThe server it runs on: id, name, address, username and status.
orderobject | nullA summary of the order: id, number and status.
optionsobjectThe service's raw settings. Password fields are stripped and the rest depends on the module.
capabilitiesobjectWhich operations this service accepts.
has_moduleboolWhether a module is attached.
can_suspendboolWhether it can be suspended.
can_unsuspendboolWhether it can be unsuspended.
can_cancelboolWhether it can be cancelled.
can_reinstallboolWhether it can be reinstalled.
can_change_passwordboolWhether its password can be changed.
Errors 2
not_found404No such service.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/services/506' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/services/506', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();

// Read the capability before attempting the operation.
if (body.data.capabilities.can_reinstall) {
  // ...
}
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$service = Api::Services()->GetService(['id' => 506])['data'];

// Capabilities come from the module: most are off on a service without one.
if ($service['capabilities']['can_suspend'] ?? false) {
    Api::Services()->SuspendService(['id' => 506]);
}

Updating a Service

patch/api/v1/admin/services/{id}
Services/UpdateService admin written to history

Changes a service's data fields. Status transitions do not happen here.

Body 23
namestringThe service name.
notesstringAn admin note. Never shown to the client.
payment_methodstringThe payment method. Sending it empty removes the method.
modulestringThe module running the service. Changeable on domain services only.
client_idintMoves the service to another client.
product_idintChanges the product behind it.
product_groupstringResolves the type when changing product. Sent together with the product id.
subscription_identifierstringThe subscription identifier. Sending it empty breaks the link.
created_atdatetimeThe opening date.
renewal_atdatetimeThe renewal date.
due_atdatetimeThe term end date. Sending it empty leaves the service without one.
suspend_datedateA scheduled suspension date. Sending it empty cancels the plan.
cancel_datedateA scheduled cancellation date.
process_exemption_datedateThe date until which automatic operations skip it.
amountfloatThe per-period amount. As a plain number, not in the display format.
currency_idintCurrency of the amount.
cyclestringThe billing cycle. Ignored on domain services.
qtyintThe quantity. The total is recalculated.
auto_payboolCharges automatically on renewal.
block_accessboolCuts the client's access to the service.
skip_renewal_invoiceboolStops the renewal invoice being produced.
billing_profile_idintThe profile the invoice is issued under. Zero goes back to the default.
discountobjectA discount applied to this service's renewal invoices. Sending an empty object removes it.
typestringpercent or amount.
valuefloatThe percentage or the amount. Above zero, and a percentage stays below a hundred.
cidintCurrency of a fixed amount. Ignored on a percentage.
ends_atstringThe last day the discount applies. Left empty it never expires.
cycles_limitintHow many renewals it covers. Zero means no limit.
notestringAn internal note on why the discount exists.
Response fields data — 26
dataobjectThe service as it now stands. Same shape as the detail endpoint.
Errors 5
not_found404No such service.
owner_not_found422The target client was not found.
invalid_date422A date could not be read.
discount_invalid422The discount was refused. The value is out of range, the end date has passed, or the service belongs to a subscription.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/services/506' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"name":"Mail Hosting Pro","amount":29.9,"qty":2,"auto_pay":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    name: 'Mail Hosting Pro',
    amount: 29.9,
    qty: 2,
    auto_pay: true,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'name'   => 'Mail Hosting Pro',
        'amount' => 29.9,
        'qty'    => 2,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// An update that moves the term end also shifts add-ons falling on the same day.
$response = Api::Services()->UpdateService([
    'id'     => 506,
    'due_at' => '2026-08-18 12:00:00',
]);

Deleting a Service

delete/api/v1/admin/services/{id}
Services/DeleteService admin cannot be undone

Deletes the service record, and if you ask, closes the account at the provider too.

Body 1
apply_on_moduleboolAlso cancels the account at the provider. Off by default: the record goes and the account stays on the server.
Response fields data — 2
deletedboolWhether the delete succeeded.
idintId of the deleted service.
Errors 2
not_found404No such service.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/services/510' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"apply_on_module":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/510', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ apply_on_module: true }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/510');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['apply_on_module' => true]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Without the flag the account on the server STAYS UP and keeps consuming resources.
$response = Api::Services()->DeleteService([
    'id'              => 510,
    'apply_on_module' => true,
]);

Pitfalls

Deleting does not close the account on the server

By default the delete removes only the record; the account at the provider stays up and keeps consuming resources. Closing it too means putting the flag in the request. Once the record is gone there is no way left to do it through the API.

Status changes are not on this endpoint

The update endpoint takes no status field: suspending, cancelling and reactivating live on their own endpoints. That is because those transitions make the module do work, and a plain field write would change nothing on the server.

Pagination sits at the root, not under meta

The service list returns total, page, limit and next_page at the root. Many other lists put them under meta, so a shared pagination helper has to account for it.

The term end date shifts add-ons too

Changing the term end also moves the end date of add-ons falling on the same day. That is usually what you want, but it is silent: you think you changed one date while other billable items moved with it.

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.