Bot and Spam Protection
The ten endpoints that stop unwanted traffic with a bot shield, a captcha and spam checks.
Overview
These ten endpoints stop unwanted traffic in three separate layers. The bot shield counts repeated attempts and cuts them off. The captcha puts a check in front of forms. The spam protection looks at what was submitted and at the visitor's reputation.
The three are independent and each has its own switch. Configuring one layer leaves the others alone, so protecting a form means switching on the right layer.
Reference
Reading the Bot Shield
Returns when repeated failed attempts get stopped.
curl 'https://panel.example.com/api/v1/admin/settings/security/bot-shield' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/bot-shield', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/bot-shield');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The attempt count is kept PER OPERATION: lowering it for one leaves the others alone.
$shield = Api::Settings()->GetBotShield()['data'];
$signIn = $shield['attempts']['sign-in'] ?? null;Writing the Bot Shield
Switches the shield on and writes the counting window and the attempt limits.
curl -X PUT 'https://panel.example.com/api/v1/admin/settings/security/bot-shield' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":1,"within_time":{"hour":60},"attempts":{"sign-in":5}}'const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/bot-shield', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 1,
within_time: { hour: 60 },
attempts: { 'sign-in': 5 },
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/bot-shield');
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,
'within_time' => ['hour' => 60],
'attempts' => ['sign-in' => 5],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Set the limit too low and REAL clients behind a shared connection get blocked as well.
Api::Settings()->UpdateBotShield([
'status' => 1,
'within_time' => ['hour' => 60],
'attempts' => ['sign-in' => 5],
]);Reading the Captcha Settings
Returns which provider is used and which forms are protected.
curl 'https://panel.example.com/api/v1/admin/settings/security/captcha' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/captcha', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/captcha');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// With the main switch off the protected list still reads FULL while none of it is enforced.
$cap = Api::Settings()->GetCaptcha()['data'];
$live = $cap['status'] === 1;Writing the Captcha Settings
Writes the provider, the protected forms and the provider's own settings.
contact-form, sign-up, sign-in, sign-forget, customer-feedback, newsletter, domain-check, software-license. Either a list or a map.curl -X PUT 'https://panel.example.com/api/v1/admin/settings/security/captcha' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status":1,"type":"DefaultCaptcha","protected_areas":["sign-in","sign-up"]}'const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/captcha', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
status: 1,
type: 'DefaultCaptcha',
protected_areas: ['sign-in', 'sign-up'],
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/captcha');
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,
'type' => 'DefaultCaptcha',
'protected_areas' => ['sign-in', 'sign-up'],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Changing provider REQUIRES its own settings: a provider with no keys breaks the form.
$fields = Api::Settings()->GetCaptchaFields(['module' => 'ReCaptcha'])['data'];
Api::Settings()->UpdateCaptcha([
'status' => 1,
'type' => 'ReCaptcha',
'config' => ['site_key' => $siteKey, 'secret_key' => $secret],
]);Listing the Providers
Returns the captcha providers installed.
curl 'https://panel.example.com/api/v1/admin/settings/security/captcha/modules' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/captcha/modules', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/captcha/modules');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The built-in provider comes FIRST in the list and needs no settings; the others need keys.
$modules = Api::Settings()->GetCaptchaModules()['data'];Reading a Provider's Fields
Returns which settings a provider wants and what is stored for them.
curl 'https://panel.example.com/api/v1/admin/settings/security/captcha/ReCaptcha/fields' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/captcha/ReCaptcha/fields', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/captcha/ReCaptcha/fields');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Learn what a provider wants from here BEFORE switching to it.
$needs = Api::Settings()->GetCaptchaFields(['module' => 'ReCaptcha'])['data']['fields'];Reading the Spam Protection
Returns the word filter, the outside reputation service and the proxy check.
curl 'https://panel.example.com/api/v1/admin/settings/security/spam' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/spam', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/spam');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The outside service does not run WITHOUT A KEY: it can read as on while no lookup happens.
$spam = Api::Settings()->GetSpamProtection()['data'];
$live = $spam['api_status'] === 1 && $spam['api_key'] !== '';Writing the Spam Protection
Writes the word filter and the outside reputation check.
curl -X PUT 'https://panel.example.com/api/v1/admin/settings/security/spam' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"word_list":"spam,scam","block_temporary":1}'const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/spam', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
word_list: 'spam,scam',
block_temporary: 1,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/spam');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'word_list' => 'spam,scam',
'block_temporary' => 1,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Turning the service on needs a KEY; send both or the check quietly does nothing.
Api::Settings()->UpdateSpamProtection([
'api_status' => 1,
'api_key' => $key,
'api_risk_score' => 25,
]);Listing What Was Blocked
Returns the requests blocked recently and the total number blocked.
curl 'https://panel.example.com/api/v1/admin/settings/security/spam-records' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/spam-records', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/spam-records');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The counter and the list are NOT the same thing: the total can far exceed what the list shows.
$rec = Api::Settings()->GetSpamRecords();
$shown = count($rec['data']);
$total = $rec['meta']['total_blocked'];Clearing What Was Blocked
Clears the blocked records and resets the total counter.
curl -X DELETE 'https://panel.example.com/api/v1/admin/settings/security/spam-records' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/settings/security/spam-records', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/settings/security/spam-records');
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);// The clear resets the COUNTER as well: how many were blocked over the installation's life is lost.
// Read it and keep it on your side first.
$total = Api::Settings()->GetSpamRecords()['meta']['total_blocked'];
Api::Settings()->ClearSpamRecords();Pitfalls
While the captcha's main switch is off, the protected form list still reads full and none of it is enforced. Adding a form to that list does not protect it; the main switch has to be on as well. The same holds for the bot shield.
The built-in captcha runs with no settings; the others need keys. Switching provider without supplying them breaks the forms: the check never loads and clients cannot submit. Read what the provider wants from the fields endpoint before switching.
Switching the outside reputation service on is not enough on its own: with an empty key no lookup happens and no visitor is checked. The setting reads as on and no error is raised. Send both together, then watch whether the blocked records start growing.
The bot shield counts attempts by address. Clients behind a shared connection, in an office or a school, appear as one address; with a low limit they spend each other's attempts and none of them gets in. When lowering the limit, weigh the counting window with it.
Clearing the blocked records does not merely empty the list: the total counter of everything blocked over the installation's life is reset with it. That number never comes back, so read it and keep it on your side first. The list itself only holds a recent slice anyway, and the total can be far larger.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.