Activity Logs
The fourteen endpoints that read and clear sent messages, sign-ins, security incidents, actions and module exchanges.
Overview
These fourteen endpoints read and clear the books of what the system did. They cover e-mails and messages sent, sign-ins and the security incidents they opened, user actions, module exchanges with providers, and database queries.
E-mail and message bodies are stored encrypted and do not come back in the list; a separate endpoint decodes them. The other books read plainly.
Every clear shares one shape: you give a cut-off date and everything on and before it goes. The one exception is the query log, which is file-based and takes no date.
Reference
Listing the E-mail Log
Returns the record of e-mails sent. The body is not in the list.
YYYY-MM-DD HH:MM:SS. The body field is not among these; the list stays light because bodies are large.curl -G 'https://panel.example.com/api/v1/admin/tools/logs/mail' \
-H "Authorization: Bearer $API_KEY" \
-d limit=50const url = new URL('https://panel.example.com/api/v1/admin/tools/logs/mail');
url.searchParams.set('limit', '50');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/logs/mail?' . http_build_query(['limit' => 50]);
$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);// The body is stored encrypted and is NOT in the list; you get it from the decode endpoint.
$logs = Api::Tools()->GetMailLogs([], ['limit' => 50])['data'];
$content = Api::Tools()->GetLogPreview(['type' => 'mail', 'id' => $logs[0]['id']]);Clearing the E-mail Log
Deletes e-mail records older than the date you give.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/mail' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"before":"2026-01-01"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/mail', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ before: '2026-01-01' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/mail');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['before' => '2026-01-01']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The response does not say HOW MANY were deleted; count them by listing first.
Api::Tools()->ClearMailLogs(['before' => '2026-01-01']);Listing the SMS Log
Returns the record of text messages sent. The body is not in the list.
admin or client.YYYY-MM-DD HH:MM:SS. The body field is missing here for the same reason as the e-mail log.curl 'https://panel.example.com/api/v1/admin/tools/logs/sms' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/sms', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/sms');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Tools()->GetSmsLogs();Clearing the SMS Log
Deletes text message records older than the date you give.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/sms' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"before":"2026-01-01"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/sms', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ before: '2026-01-01' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/sms');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['before' => '2026-01-01']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);Api::Tools()->ClearSmsLogs(['before' => '2026-01-01']);Listing the Sign-in Log
Returns the record of sign-ins to the panel and the client area.
client or admin. It defaults to client. member is read as client and staff as admin; any other value is refused.YYYY-MM-DD HH:MM:SS. The type filter works on the joined account, so a record always belongs to one side or the other.curl -G 'https://panel.example.com/api/v1/admin/tools/logs/login' \
-H "Authorization: Bearer $API_KEY" \
-d type=adminconst url = new URL('https://panel.example.com/api/v1/admin/tools/logs/login');
url.searchParams.set('type', 'admin');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/logs/login?' . http_build_query(['type' => 'admin']);
$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);// Without a type you get CLIENT sign-ins; staff sign-ins answer to 'admin' (or its alias 'staff').
$staff = Api::Tools()->GetLoginLogs([], ['type' => 'admin']);Listing Sign-in Attempts
Returns every sign-in attempt recorded on the client and staff doors, successful and failed alike, through every method. A row exists even when no session was opened.
client or admin. It defaults to client. member is read as client and staff as admin; any other value is refused.success, invalid, inactive, blocked, country, veto, disabled, two-factor, two-factor-failed, proxy or throttled. Any other value is refused.password, jetpass, passkey, social or sso. Any other value is refused.US. Any other shape is ignored.YYYY-MM-DD or an inclusive span YYYY-MM-DD - YYYY-MM-DD.YYYY-MM-DD HH:MM:SS.member for the client door, admin for the staff door.result filter values.method filter values.curl -G 'https://panel.example.com/api/v1/admin/tools/logs/login-attempts' \
-H "Authorization: Bearer $API_KEY" \
-d type=client \
-d result=invalidconst url = new URL('https://panel.example.com/api/v1/admin/tools/logs/login-attempts');
url.searchParams.set('type', 'client');
url.searchParams.set('result', 'invalid');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/logs/login-attempts?' . http_build_query(['type' => 'client', 'result' => 'invalid']);
$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);// Failed password attempts on the client door; the e-mail that was tried never comes back, only its hash.
$failed = Api::Tools()->GetLoginAttempts([], ['type' => 'client', 'result' => 'invalid']);Listing Security Events
Returns the incidents the account protection engine opened. An incident is one address reaching too many unrelated client accounts, or a risky sign-in that went through unchallenged. Attack mode switching on or off and a canary account being used open one too. Unlike the bell notice, a row stays until an operator resolves or ignores it.
account-takeover-suspected, account-protection-no-channel, attack-mode-on, attack-mode-off or canary-hit. Any other value is refused.info, warning or critical. Any other value is refused.open, resolved or ignored. Any other value is refused.YYYY-MM-DD or an inclusive span YYYY-MM-DD - YYYY-MM-DD.YYYY-MM-DD HH:MM:SS.type filter values.info, warning or critical.AS64496. Empty when unknown.id, full_name and email, one object per id. Name and e-mail are empty when the account no longer exists.accounts (how many unrelated accounts the address reached), minutes (the window), last_user_id and flags (the risk signals that fired). A no-channel event carries flags and subject_user_name. Attack mode events carry window_minutes, attempts, threshold and multi_accounts.open, resolved or ignored.action (lock-accounts, block-ip, watch-asn or auto-sessions, the last being the engine's automatic protection), admin_id (the staff account that ran it, 0 for the engine), time and result (what the action returned, see the action endpoint).details.allowed lists them.curl -G 'https://panel.example.com/api/v1/admin/tools/logs/security-events' \
-H "Authorization: Bearer $API_KEY" \
-d status=open \
-d severity=criticalconst url = new URL('https://panel.example.com/api/v1/admin/tools/logs/security-events');
url.searchParams.set('status', 'open');
url.searchParams.set('severity', 'critical');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/logs/security-events?' . http_build_query(['status' => 'open', 'severity' => 'critical']);
$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);// Open critical incidents first: the suspected takeovers still waiting for an operator.
$open = Api::Tools()->GetSecurityEvents([], ['status' => 'open', 'severity' => 'critical']);Running an Action on a Security Event
Runs one operator action on an event and writes a staff action-log line for it. The actions are the same ones the Security Events screen and the alert e-mail offer. The lock action gives every tied client account a new random password; it cannot be undone.
lock locks every client account tied to the event (random password, so sessions and "remember me" tokens drop; trusted devices forgotten; a reset e-mail sent) and skips staff and missing accounts. block shuts the event's address out of the password and JetPass doors for 24 hours. watch adds the event's network (ASN) to the watched list under Account Protection. resolve, ignore and reopen only change the status. Any other value is refused.lock: locked (account ids that were locked), reset_sent (ids a reset e-mail went out to) and skipped (staff or missing ids). block: ip, until (block expiry), doors (the doors blocked now) and already (true when both doors were blocked before). watch: asn, already and list (the watched networks after the change). The status actions return status, the new state.details.allowed lists them.curl -X POST 'https://panel.example.com/api/v1/admin/tools/security-events/18/action' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"action":"block"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/security-events/18/action', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'block' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/security-events/18/action');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['action' => 'block']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// A second block on the same event does not fail; it comes back with already => true.
$result = Api::Tools()->SecurityEventAction(['id' => 18], ['action' => 'block']);Listing the Action Log
Returns the record of what users and the system did.
client, admin or system. It defaults to client. member is read as client and staff as admin; any other value is refused.type=admin, a client's id with type=client. A value that is not a positive whole number is ignored. System rows belong to no account, so with type=system the list comes back empty.update, deletion, create and the like.description rather than this key.locale_detail when the key has no sentence any more.YYYY-MM-DD HH:MM:SS. Module records share this table but stay out of this listing; they have their own endpoint.curl -G 'https://panel.example.com/api/v1/admin/tools/logs/actions' \
-H "Authorization: Bearer $API_KEY" \
-d type=adminconst url = new URL('https://panel.example.com/api/v1/admin/tools/logs/actions');
url.searchParams.set('type', 'admin');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/tools/logs/actions?' . http_build_query(['type' => 'admin']);
$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::Tools()->GetActionLogs([], ['type' => 'admin']);Clearing the Action Log
Deletes action records older than the date you give. It leaves the module log alone.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/actions' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"before":"2026-01-01"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/actions', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ before: '2026-01-01' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/actions');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['before' => '2026-01-01']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// This clear does NOT sweep the module log; that has its own endpoint.
Api::Tools()->ClearActionLogs(['before' => '2026-01-01']);
Api::Tools()->ClearModuleLogs(['before' => '2026-01-01']);Listing the Module Log
Returns the record of what modules exchanged with providers.
module-log on this endpoint.YYYY-MM-DD HH:MM:SS. These records share a table with the action log, but clearing one leaves the other untouched.curl 'https://panel.example.com/api/v1/admin/tools/logs/module' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/module', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/module');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The module log only fills while it is ON in the log settings; an empty list is not 'all fine'.
$settings = Api::Tools()->GetLogSettings()['data'];
if ($settings['module_log']) {
$logs = Api::Tools()->GetModuleLogs();
}Clearing the Module Log
Deletes module records older than the date you give.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/module' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"before":"2026-01-01"}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/module', {
method: 'DELETE',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ before: '2026-01-01' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/module');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['before' => '2026-01-01']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);Api::Tools()->ClearModuleLogs(['before' => '2026-01-01']);Decoding the Body
Decrypts and returns the stored body of one e-mail or text message record.
mail or sms.curl -X POST 'https://panel.example.com/api/v1/admin/tools/logs/preview' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"type":"mail","id":1240}'const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/preview', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ type: 'mail', id: 1240 }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/preview');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['type' => 'mail', 'id' => 1240]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The decrypted body carries client data: do not write it to your own logs.
$content = Api::Tools()->GetLogPreview([
'type' => 'mail',
'id' => 1240,
])['data']['content'];Deleting the Query Log
Deletes the database query log files. It takes no date and removes all of them.
curl -X DELETE 'https://panel.example.com/api/v1/admin/tools/logs/query' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/tools/logs/query', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/tools/logs/query');
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 takes NO date: every file goes. Writing them is switched off in the settings.
Api::Tools()->ClearQueryLogs();
Api::Tools()->SetLogSettings(['query_logging' => 0]);Pitfalls
The content of e-mail and message records is stored encrypted and never comes back from the listing endpoints. Seeing what was sent means calling the decode endpoint separately, one request per record. The decrypted text carries client data, so do not store it on your side.
The module and query logs only fill while they are on in the log settings. With them off the lists come back empty, which does not mean nothing happened. Confirm in the settings that the log you need is on before investigating anything.
On the sign-in and action logs, leaving the type out gives you the client side. Looking for admin sign-ins or the system's own actions means sending the type explicitly, or you conclude the record you want never existed.
Clearing the action log leaves the module log where it is; they are separate endpoints. Making one call to free space, you may not notice the bigger book is still sitting there.
The date-based clears return only that they ran and which date they used; they give no count. To measure the effect, list the same range and count it first.
The lock action gives every tied client account a new random password. There is no undo; the owner must use the reset e-mail. block and watch do not fail on a second call: they return already: true and change nothing. Read that flag instead of taking a 200 as "the block was placed now".
Related Articles
¡Gracias por sus comentarios!
Nuestro equipo de soporte está disponible las 24 horas para ayudarle con lo que no encuentre aquí.