Sub-users
The five endpoints that manage the limited-permission accounts reaching a client's panel.
Overview
A sub-user is a second person who reaches a client's panel with limited permissions: the accountant sees only invoices, the technical team only services.
The permission set is fixed and holds ten values. The owner's own profile, billing contacts and e-mail history cannot be handed over; those stay with the account itself.
Reference
Listing the Sub-users
Returns every sub-user the client has.
pending is awaiting the invite, active is live, inactive is switched off.0 when there is none.curl 'https://panel.example.com/api/v1/admin/clients/42/subusers' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/subusers', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/subusers');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Clients()->GetClientSubusers(['id' => 42]);{
"data": [
{
"id": 4,
"email": "[email protected]",
"label": "Accounting",
"status": "active",
"permissions": ["view_invoices", "view_services"],
"email_notifications": 0,
"sms_notifications": 0,
"linked_user_id": 31,
"linked_user_name": "John Doe",
"invited_at": "2026-06-01 10:00:00",
"accepted_at": "2026-06-02 09:12:00",
"created_at": "2026-06-01 10:00:00"
}
]
}Adding a Sub-user
Adds a sub-user to the client. The record starts out pending.
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/subusers' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","label":"Accounting","permissions":["view_invoices","view_services"]}'const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/subusers', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: '[email protected]',
label: 'Accounting',
permissions: ['view_invoices', 'view_services'],
send_invite: true,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/subusers');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'email' => '[email protected]',
'label' => 'Accounting',
'permissions' => ['view_invoices', 'view_services'],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Clients()->CreateClientSubuser([
'id' => 42,
'email' => '[email protected]',
'permissions' => ['view_invoices', 'view_services'],
]);
// Read the granted permissions back: an unrecognised key is dropped silently.
$granted = $response['data']['permissions'];Updating a Sub-user
Updates the fields you send and leaves the rest alone.
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/42/subusers/5' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":"active"}'const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/subusers/5', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ status: 'active' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/subusers/5');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['status' => 'active']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Clients()->UpdateClientSubuser([
'id' => 42,
'subuser_id' => 5,
'status' => 'active',
]);
// Going active for the first time links a real account when the e-mail matches.
$linkedTo = $response['data']['linked_user_id'];Resending the Invite
Sends the invite again to a sub-user still waiting for one.
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/subusers/5/resend-invite' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/subusers/5/resend-invite', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/subusers/5/resend-invite');
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);$response = Api::Clients()->ResendClientSubuserInvite([
'id' => 42,
'subuser_id' => 5,
]);Deleting a Sub-user
Deletes the sub-user. The real client account it was linked to is not affected.
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/42/subusers/5' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/subusers/5', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/subusers/5');
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);$response = Api::Clients()->DeleteClientSubuser([
'id' => 42,
'subuser_id' => 5,
]);Pitfalls
The permission list is a closed set. Send a key that is not in it and you get no error; the key is thrown away and the record is stored with fewer permissions than you meant. Read the permissions field back from the write to see what was granted.
The first time a sub-user is set to active, a matching e-mail on an existing client account links that account. That means someone can sign in as themselves and step into another client's panel, so know whose e-mail it is before changing status.
The resend endpoint only works on a record that is still pending. To invite a live sub-user you have to move the status back first.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.