Service Settings and Server

7 views Markdown

The four endpoints behind a service's access details, limits, server and information blocks.

Overview

These four endpoints touch a service's settings. The update on the service endpoints handles billing and dates; these decide how the service is reached, which server it sits on, and what the client reads on their page.

The management form behaves differently per service type: access details and limits are handled on hosting, server and special products, while licence fields apply to software services only. A field sent to the wrong type is ignored without a word.

Reference

Saving the Management Form

patch/api/v1/admin/services/{id}/management
Services/UpdateServiceManagement admin depends on the type

Saves a service's access details, resource limits and licence fields.

Body 8
accessobjectHow the service is reached. Only the fields you send change; the rest stay as they are.
domainstringThe domain tied to the service.
hostnamestringThe host name.
ipstringThe service address.
usernamestringThe sign-in username.
passwordstringThe sign-in password. Stored encrypted and never read back.
override_product_limitsboolOverrides the product's limits for this service. Limits you send without turning this on are not applied.
limitsobjectThe resource limits. The fields follow the service type: disk and bandwidth on hosting, processor, memory, disk and bandwidth on a server.
modulestringThe module running the service. On special products only.
licenseobjectThe licence fields. Handled on software services only.
domainstringThe domain the licence is locked to.
ipstringThe address the licence is locked to.
codestringThe licence key.
license_parametersobjectExtra fields the licence carries.
creation_infoobjectThe module's creation info. Merged into what is already there.
configobjectThe module settings. Merged into what is already there.
configurationobjectThe module configuration. Merged into what is already there.
Response fields data
dataobjectThe service as it now stands. Same shape as the detail endpoint in the service endpoints article.
The password never comes back in the answer. Read options to confirm the access details that are not secret.
Errors 2
not_found404No such service.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/services/506/management' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"access":{"username":"john"},"override_product_limits":true,"limits":{"disk_limit":50}}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/management', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    access: { username: 'john' },
    override_product_limits: true,
    limits: { disk_limit: 50 },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/management');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'access'                  => ['username' => 'john'],
        'override_product_limits' => true,
        'limits'                  => ['disk_limit' => 50],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Sending limits is not enough: turn the override on first, or the product's limits still rule.
$response = Api::Services()->UpdateServiceManagement([
    'id'                      => 506,
    'override_product_limits' => true,
    'limits'                  => ['disk_limit' => 50, 'bandwidth_limit' => 500],
]);

Moving to Another Server

put/api/v1/admin/services/{id}/server
Services/SetServiceServer admin no data is moved

Points the service record at another server and updates the module to match.

Body 1
server_idintrequiredId of the target server. Sending zero leaves the service without a server and drops the module.
Response fields data — 3
server_idintId of the new server.
modulestringThe module the target server brings. none when left without a server.
idintService id.
Errors 4
not_found404No such service.
same_server422The service is already on that server.
server_not_found422The target server does not exist.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/services/506/server' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"server_id":5}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/server', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ server_id: 5 }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This endpoint moves the RECORD only; building the account on the new server is separate work.
$response = Api::Services()->SetServiceServer([
    'id'        => 506,
    'server_id' => 5,
]);

$module = $response['data']['module'];
Response
{
  "data": {
    "server_id": 5,
    "module": "Mailcow",
    "id": 506
  }
}
{
  "error": {
    "code": "same_server",
    "message": "Service is already on this server."
  }
}

Writing the Information Blocks

put/api/v1/admin/services/{id}/blocks
Services/SetServiceBlocks admin the list is written whole

Writes the free-text blocks the client sees on the service page.

Body 1
blocksarrayrequiredThe list of blocks. Each carries a title and a description. A block empty in both is skipped, and a second name is accepted for the description field.
Response fields data — 2
blocksarrayThe blocks that were stored.
idintService id.
Errors 3
not_found404No such service.
invalid_blocks422blocks is not an array.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/services/506/blocks' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"blocks":[{"title":"Connection","description":"Connect over SSH on port 22."}]}'
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/blocks', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    blocks: [
      { title: 'Connection', description: 'Connect over SSH on port 22.' },
    ],
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/blocks');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'blocks' => [
            ['title' => 'Connection', 'description' => 'Connect over SSH on port 22.'],
        ],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// To ADD a block send the existing list too: what you send becomes the truth.
$service = Api::Services()->GetService(['id' => 506])['data'];
$blocks  = $service['options']['blocks'] ?? [];

$blocks[] = ['title' => 'Backups', 'description' => 'Nightly, kept for 7 days.'];

Api::Services()->SetServiceBlocks(['id' => 506, 'blocks' => $blocks]);

Clearing the Status Message

post/api/v1/admin/services/{id}/clear-status-message
Services/ClearServiceStatusMessage admin

Removes the sticky status message the module left behind.

Body
No body is needed. The service comes from the path and the whole message always goes; send an empty body.
Response fields data — 2
clearedboolWhether it was cleared.
idintService id.
Errors 2
not_found404No such service.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/services/506/clear-status-message' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/services/506/clear-status-message', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/services/506/clear-status-message');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Clearing the message does not fix its cause: the module can write the same error again.
$response = Api::Services()->ClearServiceStatusMessage(['id' => 506]);

Pitfalls

Changing the server does not move the data

This endpoint only points the record at the new server and swaps the module to match. The account stays exactly where it was on the old server and is never created on the new one. The real migration is separate work; this call is for lining the record up afterwards.

Limits do nothing until the flag is on

Sending resource limits is not enough on its own: with override_product_limits off the product's limits still rule and your values are not applied. No error is raised either, so read the service detail back to confirm.

The block list is written whole

What you send becomes the truth: blocks missing from the list are deleted. To add one, read the current list first, append to it and send all of it back. A block empty in both title and description is skipped without a word.

Clearing the message does not fix its cause

The status message is a trace the module left. Clearing it removes the text only; if the same error persists the module writes it again on its next operation. Look at why it was written before hiding 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.