Quick Reads
The six read-only endpoints that search clients, give the counts, fill the summary card and list a client's activity and sent messages.
Overview
These six endpoints tell you something quickly about a client and change nothing. The panel's search box, dashboard counters and summary card all read from them, and the two list endpoints return the same records as the Activity and Messages tabs on the client page.
Two look across the installation (search and statistics) and four at a single client (summary, the standout note, activity and sent messages). Whether the path carries a client id tells you which one you are on.
Reference
Searching for a Client
Searches clients by name, company or e-mail. It was written for autocomplete boxes.
search is accepted too.curl -G 'https://panel.example.com/api/v1/admin/clients/search' \
-H "Authorization: Bearer $API_KEY" \
-d q=acmeconst url = new URL('https://panel.example.com/api/v1/admin/clients/search');
url.searchParams.set('q', 'acme');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/clients/search?' . http_build_query(['q' => 'acme']);
$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);$response = Api::Clients()->SearchClients([], ['q' => 'acme']);Client Statistics
Returns the counts across every client in the installation. It looks at no single client.
all_time, today, week, month or year. Defaults to all_time.curl -G 'https://panel.example.com/api/v1/admin/clients/stats' \
-H "Authorization: Bearer $API_KEY" \
-d period=monthconst url = new URL('https://panel.example.com/api/v1/admin/clients/stats');
url.searchParams.set('period', 'month');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/clients/stats?' . http_build_query(['period' => 'month']);
$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);$response = Api::Clients()->GetClientsStats([], ['period' => 'month']);{
"data": {
"active": 120,
"blocked": 3,
"new": 18,
"blacklisted": 2,
"active_services": 340,
"unpaid_invoices": 41,
"credit_balance": 14620.12,
"growth_rate": 4.5,
"total": 145
}
}Client Summary
Returns summary card data for one client: revenue, service and ticket counts, badges, trust score.
reference/currencies.poor, fair, good ya da excellent.curl 'https://panel.example.com/api/v1/admin/clients/42/summary' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/summary', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/summary');
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()->GetClientSummary(['id' => 42]);
// The parts tell you where the score came from.
$score = $response['data']['trust_score'];
$fromServices = $score['services'];{
"data": {
"user_id": 9,
"full_name": "Test Client",
"company_name": "",
"created_at": "2021-03-18 00:00:00",
"total_revenue": 36,
"revenue_currency": 4,
"paid_invoices": 1,
"active_services": 16,
"inactive_services": 16,
"total_tickets": 0,
"recent_tickets": 0,
"badges": {
"loyal": true,
"revenue": false,
"multi_service": true,
"experienced": true
},
"trust_score": {
"total": 50,
"label": "fair",
"services": 30,
"revenue": 5,
"age": 15,
"tickets": 0
}
}
}Reading the Standout Note
Returns one of the client's notes: the pinned one if there is one, otherwise the newest.
null when the note is not pinned, or was pinned before this field existed.curl 'https://panel.example.com/api/v1/admin/clients/42/latest-note' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/latest-note', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
// With no notes at all, data comes back null - not a 404.
if (body.data === null) return;$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/latest-note');
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()->GetClientLatestNote(['id' => 42]);
// On a client with no notes, 'data' is null.
$note = $response['data'] ?? null;Listing a Client's Activity
Lists one client's activity records, newest first: what the client did and what staff or the system did on the account. These are the records of the Activity tab on the client page, with the same search and date filters.
1.1 to 100. Defaults to 25; a larger value is capped at 100.Y-m-d, or a range written as two dates joined by a spaced hyphen: 2026-10-01 - 2026-10-12.date is compared: eq, neq, gte, gt, lte or lt. Defaults to eq: on that day or inside that range.0 for the system.client (the client or one of its users), staff, system, or deleted when the acting account no longer exists.in, alteration, login, added or delete.Y-m-d H:i:s).0.date is neither Y-m-d nor a Y-m-d - Y-m-d range.date_op is not one of the six operators.curl -G 'https://panel.example.com/api/v1/admin/clients/42/activity' \
--data-urlencode 'search=login' \
--data-urlencode 'date=2026-10-01 - 2026-10-12' \
-H "Authorization: Bearer $API_KEY"const query = new URLSearchParams({ search: 'login', limit: '50' });
const res = await fetch(`https://panel.example.com/api/v1/admin/clients/42/activity?${query}`, {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
// meta.next_page is 0 on the last page.
const more = body.meta.next_page !== 0;$query = http_build_query(['date' => '2026-10-01', 'date_op' => 'gte', 'limit' => 50]);
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/activity?' . $query);
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()->GetClientActivity(['id' => 42], ['search' => 'login', 'limit' => 50]);
foreach ($response['data'] ?? [] as $row)
echo $row['created_at'], ' ', $row['description'], PHP_EOL;{
"data": [
{
"id": 183379,
"actor_id": 1,
"actor_type": "staff",
"actor_name": "Admin",
"reason": "login",
"action": "admin-login-as-client",
"description": "Logged in as client John Doe (#116)",
"data": { "user_id": 116, "user_name": "John Doe" },
"ip": "203.0.113.10",
"created_at": "2026-10-12 05:41:07"
}
],
"meta": { "total": 716, "page": 1, "limit": 25, "next_page": 2 }
}Listing the Messages Sent to a Client
Lists the e-mails and SMS messages sent to one client, newest first, with the same search and date filters as the Messages tab on the client page. The list carries no message body.
1.1 to 100. Defaults to 25; a larger value is capped at 100.Y-m-d, or a range written as two dates joined by a spaced hyphen: 2026-10-01 - 2026-10-12.date is compared: eq, neq, gte, gt, lte or lt. Defaults to eq: on that day or inside that range.type.email or sms.two-factor-verification, subuser-invitation or message-from-admin; empty when the send stored none.Y-m-d H:i:s).0.date is neither Y-m-d nor a Y-m-d - Y-m-d range.date_op is not one of the six operators.curl -G 'https://panel.example.com/api/v1/admin/clients/42/messages' \
--data-urlencode 'date=2026-10-01' \
--data-urlencode 'date_op=gte' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/messages?search=invoice', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
// The list has no body: keep type + id to read one message later.
const refs = body.data.map(m => ({ type: m.type, id: m.id }));$query = http_build_query(['search' => 'invoice', 'page' => 2]);
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/messages?' . $query);
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()->GetClientMessages(['id' => 42], ['date' => '2026-10-01 - 2026-10-12']);
// Read one body through the preview endpoint, with type + id from the list.
$first = $response['data'][0] ?? null;
$preview = $first ? Api::Clients()->GetMessagePreview([], ['type' => $first['type'], 'id' => $first['id']]) : null;{
"data": [
{
"id": 17313,
"type": "email",
"subject": "Support ticket created",
"recipient": "[email protected]",
"reason": "",
"ip": "203.0.113.10",
"created_at": "2026-10-11 18:02:44"
},
{
"id": 905,
"type": "sms",
"subject": "Your service is ready",
"recipient": "+15550100",
"reason": "message-from-admin",
"ip": "203.0.113.10",
"created_at": "2026-10-10 09:00:12"
}
],
"meta": { "total": 522, "page": 1, "limit": 25, "next_page": 2 }
}Pitfalls
The credit balance and revenue fields are numbers without a symbol, and the date is raw too. You read the currency from its own field and do the formatting yourself. That is deliberate: the server does not know the reader's locale.
On a client with no notes the standout-note endpoint returns data: null, not a 404. Code that reaches straight into the fields breaks here, so check for empty first.
The score in the summary is not a fixed scale; it is worked out from the tiers in your configuration. Change the tiers and the same client scores differently, so do not compare the score across installations.
The sent-messages endpoint returns only the subject, recipient and date. To read a message, pass its type and id to the message preview endpoint; an id alone is not enough, because e-mails and SMS messages are numbered separately. The preview withholds the body of security and confidential messages.
Related Articles
Merci pour votre retour !
Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.