Add-on Modules

7 views Markdown

The seven endpoints that list, configure, test and remove the installed add-on modules.

Overview

Add-on modules are pieces that extend the installation: accounting integrations, chat, translation. These seven endpoints list them, configure them, test them, run their own methods and remove them.

Every module supports different things. The capability list on the detail says what is possible. Whether it has settings, whether it offers a connection test, which methods can be called. Read the method name from that list rather than guessing it.

Reference

Listing the Add-ons

get/api/v1/admin/tools/addons
Tools/GetAddons admin

Returns the add-on modules installed.

Query parameters 3
statusstringFilters by status: enabled or disabled.
searchstringSearches the modules.
include_premiumintAlso brings modules available from the official store. Those come back under meta, not in the installed list.
Response fields data[] — 9
keystringThe module key. This goes in the path; dashes, dots and spaces become underscores.
namestringThe module name.
descriptionstringWhat it does.
authorstringIts author.
versionstringIts version.
statusboolWhether it is switched on.
premiumboolWhether it is a paid module.
logostringThe address of its logo.
installedboolWhether it is installed.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -G 'https://panel.example.com/api/v1/admin/tools/addons' \
  -H "Authorization: Bearer $API_KEY" \
  -d status=enabled
const url = new URL('https://panel.example.com/api/v1/admin/tools/addons');
url.searchParams.set('status', 'enabled');

const res  = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();
$url = 'https://panel.example.com/api/v1/admin/tools/addons?' . http_build_query(['status' => 'enabled']);

$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);
// Store modules are not in the INSTALLED list; they come separately under meta.
$response = Api::Tools()->GetAddons([], ['include_premium' => 1]);

$installed = $response['data'];
$buyable   = $response['meta']['premium'] ?? [];

Add-on Detail

get/api/v1/admin/tools/addons/{module}
Tools/GetAddon admin passwords masked

Returns an add-on's settings, its form definition and what it can do.

Response fields data — 14
keystringThe module key. This goes in the path; dashes, dots and spaces become underscores.
namestringThe module name.
descriptionstringWhat it does.
authorstringIts author.
versionstringIts version.
statusboolWhether it is switched on.
premiumboolWhether it is a paid module.
logostringThe address of its logo.
installedboolWhether it is installed.
opening_typestringHow the module opens in the panel.
access_psstring[]The privilege groups that can reach the module.
settingsobjectThe module's stored settings.
fieldsobjectThe definition of the settings form. Values on password-type fields come back masked.
capabilitiesobjectWhat the module supports.
has_settingsboolWhether it has settings.
has_testboolWhether it supports a connection test.
has_uninstallboolWhether it can be uninstalled.
has_admin_areaboolWhether it has its own page in the panel.
has_client_areaboolWhether it appears in the client area.
methodsstring[]The names of its callable methods. The run endpoint accepts only what is on this list.
Errors 3
module_required422No module key was given.
not_found404No such module.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/tools/addons/Parasut' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/addons/Parasut');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The list of callable methods comes from HERE; do not guess a method name.
$module = Api::Tools()->GetAddon(['module' => 'Parasut'])['data'];

$methods = $module['capabilities']['methods'];

Changing the Status

put/api/v1/admin/tools/addons/{module}/status
Tools/UpdateAddonStatus admin

Switches the add-on on or off.

Body 1
statusintrequired1 switches it on, 0 switches it off.
Response fields data
dataobjectThe add-on summary after the change. Same shape as a list item.
Errors 4
status_required422No status was given.
not_supported422The module does not support changing its status.
status_change_failed422The status could not be changed.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/tools/addons/Parasut/status' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":1}'
const res = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut/status', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 1 }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Switching one on runs the module's own setup: it can create tables and register hooks.
Api::Tools()->UpdateAddonStatus(['module' => 'Parasut', 'status' => 1]);

Saving the Settings

put/api/v1/admin/tools/addons/{module}/settings
Tools/UpdateAddonSettings admin do not send the mask back

Writes the module's settings and, if you like, changes its status in the same request.

Body 3
fieldsobjectThe values of the settings form. The field names come from the form definition on the detail.
access_psstring[]The privilege groups that can reach the module.
statusintThe module status. Send it and it changes alongside the settings.
Response fields data — 4
keystringThe module key.
statusboolThe status afterwards.
access_psstring[]The privilege list that was stored.
settingsobjectThe settings that were stored.
Errors 5
not_supported422The module has no settings.
settings_invalid422The settings were refused.
settings_failed422The settings could not be stored.
status_change_failed422The status could not be changed.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/tools/addons/Parasut/settings' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"fields":{"api_key":"xxxx"},"status":1}'
const res = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut/settings', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    fields: { api_key: secret },
    status: 1,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/addons/Parasut/settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'fields' => ['api_key' => $secret],
        'status' => 1,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Password fields read from the detail are MASKED: send them back as they are and you store the mask.
$module = Api::Tools()->GetAddon(['module' => 'Parasut'])['data'];
$fields = $module['settings'];

unset($fields['api_key']);      // leave it out when it should not change
$fields['webhook_url'] = $url;

Api::Tools()->UpdateAddonSettings(['module' => 'Parasut', 'fields' => $fields]);

Testing the Connection

post/api/v1/admin/tools/addons/{module}/test-connection
Tools/TestAddonConnection admin

Tries whether the module can reach its service with the settings it has.

Body
No body is needed; send an empty one. The credentials come from the module's saved settings, never from the request.
Response fields data — 2
keystringThe module key.
connectedboolWhether the connection was made.
Errors 3
test_not_supported422The module does not support a test.
test_failed422The connection could not be made.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/tools/addons/Parasut/test-connection' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut/test-connection', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/addons/Parasut/test-connection');
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);
// The test uses the STORED settings: save first, then try.
Api::Tools()->UpdateAddonSettings(['module' => 'Parasut', 'fields' => $fields]);
$test = Api::Tools()->TestAddonConnection(['module' => 'Parasut']);

Running a Module Method

post/api/v1/admin/tools/addons/{module}/methods/{method}
Tools/RunAddonMethod admin bound to an allow list

Runs one of the add-on's own methods.

Path segments 2
modulestringThe module key.
methodstringThe method name. It comes from the callable list on the detail and is written without the prefix.
Body
No body is needed; send an empty one. The module method is called with no arguments, so nothing you send reaches it.
Response fields data — 3
keystringThe module key.
methodstringThe method that ran.
resultmixedWhat the method returned. Its shape depends entirely on the module.
Errors 4
method_required422No method name was given.
method_not_found422The module has no such method.
method_failed422The method failed or returned nothing.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/tools/addons/Parasut/methods/sync' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut/methods/sync', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/addons/Parasut/methods/sync');
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);
// An empty return counts as FAILURE: a method that returns nothing answers 'method_failed'.
$module  = Api::Tools()->GetAddon(['module' => 'Parasut'])['data'];

if (in_array('sync', $module['capabilities']['methods'], true)) {
    Api::Tools()->RunAddonMethod(['module' => 'Parasut', 'method' => 'sync']);
}

Deleting an Add-on

delete/api/v1/admin/tools/addons/{module}
Tools/DeleteAddon admin the directory goes

Uninstalls the module and deletes its files from disk.

Response fields data — 2
deletedboolWhether the delete succeeded.
keystringKey of the deleted module.
Errors 5
not_found404No such module.
blocked_by_gate422A hook vetoed the removal.
uninstall_failed422The module's own uninstall step failed.
removal_failed422The files could not be deleted.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/addons/Parasut' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/tools/addons/Parasut', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/tools/addons/Parasut');
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);
// This endpoint also deletes the module's DIRECTORY: coming back means uploading it again.
// To stop it for a while, switch the status off instead of deleting.
Api::Tools()->UpdateAddonStatus(['module' => 'Parasut', 'status' => 0]);

Pitfalls

Do not send the masked password back

The detail returns password-type fields masked. Write those settings straight back and you store the mask in place of the real password. The module can then no longer reach its service. Leave password fields you are not changing out of the body entirely.

Deleting removes the directory too

The delete does not merely switch the module off: it runs the module's own uninstall and deletes its files from disk. Coming back means uploading it again. To stop a module for a while, switch its status off instead.

The test uses the stored settings

The connection test takes no body; it runs with the settings the module already has. Trying a new key means saving it first, so a failed test leaves the wrong setting already written.

An empty return counts as a failure

The run endpoint answers method_failed when the method returns nothing. So a method that genuinely ran but produced no result also looks like an error. Without knowing what the module returns, do not read that code as a definite failure.

Store modules are not in the installed list

When you ask for the purchasable modules they are not mixed into the main list. They come back in a field of their own. Every row in the main list is installed, so do not try to tell them apart by the installed field.

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.