Account State and Bulk Actions

7 views Markdown

The six endpoints that block an account, suspend or cancel its services in bulk, and chase unpaid invoices.

Overview

These endpoints change the state of an account and of the services attached to it. That covers blocking, bulk suspension, bulk cancellation and invoice reminders.

Blocking comes in two shapes. For a single client the block endpoint also stores the reason; for a list, bulk does the same job without one. The panel walks bulk service actions one at a time to drive its progress bar. The API does the whole set in one call.

Reference

Blocking an Account

put/api/v1/admin/clients/{id}/block
Clients/SetClientBlock admin sends a notification

Blocks the account or lifts the block. The client is notified.

Body 2
blockedboolrequiredtrue blocks, false lifts the block.
reasonstringWhy it was blocked. Stored only while blocking.
Response fields data — 1
blockedboolThe block state afterwards.
Errors 3
not_found404No such client.
blocked_by_gate422The gate:user.block hook vetoed the operation.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/clients/42/block' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"blocked":true,"reason":"Payment dispute"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/block', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ blocked: true, reason: 'Payment dispute' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/block');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'blocked' => true,
        'reason'  => 'Payment dispute',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->SetClientBlock([
    'id'      => 42,
    'blocked' => true,
    'reason'  => 'Payment dispute',
]);

// If a hook vetoes, the call returns an error and nothing is blocked.
if (isset($response['error'])) {
    $code = $response['error']['code'];
}

Applying a Bulk Action

post/api/v1/admin/clients/bulk
Clients/BulkClientActions admin many clients

Applies the same action to a list of clients. Only member accounts are processed; admins are skipped.

Body 2
actionstringrequiredThe action to apply. verify marks e-mail and phone as verified, reactivate puts the account back to active, block blocks it.
idsint[]requiredThe client ids.
Response fields data — 2
actionstringThe action that was applied.
processedint[]The ids that were actually processed. Skipped admin accounts are not here, so compare against the list you sent.
Errors 3
action_invalid422The action is not one of the three values.
ids_required422ids was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/bulk' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"action":"verify","ids":[80,81]}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/bulk', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ action: 'verify', ids: [80, 81] }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/bulk');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'action' => 'verify',
        'ids'    => [80, 81],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$sent     = [80, 81, 3];
$response = Api::Clients()->BulkClientActions([
    'action' => 'verify',
    'ids'    => $sent,
]);

// Compare the lists to see what was skipped (admin accounts are not processed).
$skipped = array_diff($sent, $response['data']['processed'] ?? []);
Response
{
  "data": {
    "action": "verify",
    "processed": [80, 81]
  }
}
{
  "error": {
    "code": "action_invalid",
    "message": "action must be verify/reactivate/block."
  }
}

Suspending Every Service

post/api/v1/admin/clients/{id}/services/suspend
Clients/SuspendClientServices admin no body

Suspends every active service the client has. The reason is written as Account bulk suspended.

Body
No body is needed. The client comes from the path and the set cannot be narrowed: every active service is taken.
Response fields data — 3
suspendedintHow many services were suspended.
idsint[]Ids of the suspended services.
failedobject[]The services that failed. Each element carries {id, error}. An empty array means all of them went through.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/services/suspend' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/services/suspend', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/services/suspend');
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()->SuspendClientServices(['id' => 42]);

// Partial success is possible: some go through, others come back in 'failed'.
foreach ($response['data']['failed'] ?? [] as $fail) {
    $serviceId = $fail['id'];
    $reason    = $fail['error'];
}

Lifting the Bulk Suspension

post/api/v1/admin/clients/{id}/services/unsuspend
Clients/UnsuspendClientServices admin bulk suspensions only

Puts the bulk-suspended services back. It does not touch services suspended for another reason.

Body
No body is needed. The client comes from the path and the set is fixed: the services carrying the bulk-suspension reason.
Response fields data — 3
unsuspendedintHow many services were put back.
idsint[]Ids of the services affected.
failedobject[]The services that failed. Each element carries {id, error}. An empty array means all of them went through.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/services/unsuspend' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/services/unsuspend', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/services/unsuspend');
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()->UnsuspendClientServices(['id' => 42]);

Cancelling Every Service

post/api/v1/admin/clients/{id}/services/cancel
Clients/CancelClientServices admin cannot be undone

Cancels every service the client has that is not already cancelled or finished.

Body
No body is needed. The client comes from the path; there is no field to pick single services with.
Response fields data — 3
cancelledintHow many services were cancelled.
idsint[]Ids of the cancelled services.
failedobject[]The services that failed. Each element carries {id, error}. An empty array means all of them went through.
Errors 3
not_found404No such client.
blocked_by_gate422The gate:user.services_bulk_cancel hook vetoed the operation.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/services/cancel' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/services/cancel', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/services/cancel');
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()->CancelClientServices(['id' => 42]);

Reminding About Unpaid Invoices

post/api/v1/admin/clients/{id}/remind-invoices
Clients/RemindClientInvoices admin one notice per invoice

Sends a reminder notice for every unpaid invoice the client has.

Body
No body is needed. The client comes from the path; every unpaid invoice gets a notice, and single ones cannot be chosen.
Response fields data — 1
remindedintHow many invoices got a reminder.
Errors 3
not_found404No such client.
no_unpaid_invoices422The client has no unpaid invoices. You get an error, not an empty response.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/remind-invoices' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/remind-invoices', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/remind-invoices');
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()->RemindClientInvoices(['id' => 42]);

// No unpaid invoices is an error here, not an empty result.
if (($response['error']['code'] ?? '') === 'no_unpaid_invoices') {
    return;
}

Pitfalls

Partial success is quiet

The bulk service endpoints can return 200 while some services still failed; those come back in the failed array as {id, error}. A client that only reads the status code will miss it.

Unsuspending is selective

The unsuspend endpoint only reopens services that were stopped by the bulk suspension. A service suspended for overdue payment or by hand stays where it is, which stops one call from silently restarting it.

Bulk actions skip admin accounts

An account in your list that is not a member gets skipped without a word. It does not appear in processed, so compare the list you sent with the one you get back.

The panel asks for a password, the API for a scope

Bulk cancellation asks for the admin password in the panel. The API has no such second step: the key's scope is enough. Hand out a key carrying this scope knowing exactly who holds 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.