Blacklist and Duplicates

8 views Markdown

The four endpoints that blacklist a client, take them off the list, and find accounts that might be the same person.

Overview

These endpoints do two jobs together: blacklisting a client, and finding other accounts that might be the same person. They belong to one decision — someone who gets blacklisted usually comes back with a second account.

Blacklisting is more than a flag: you choose which restrictions come with it. Switch none on and the client is only marked.

Reference

Blacklist Status

get/api/v1/admin/clients/{id}/blacklist
Clients/GetClientBlacklist admin

Returns whether the client is blacklisted, why, and which restrictions are on.

Response fields data — 6
blacklistedboolWhether the client is blacklisted.
reasonstringThe reason. Values: payment_fraud · chargeback · abuse · spam · tos_violation · false_info · other
notesstringA free-text note.
restrictionsobjectThe restrictions that are on.
block_new_ordersboolBlocks new orders.
block_renewalsboolBlocks renewals.
block_ticketsboolBlocks opening tickets.
suspend_servicesboolSuspends the services.
blacklisted_bystringName of the admin who blacklisted.
blacklisted_atstringWhen the client was blacklisted.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/42/blacklist' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/42/blacklist', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/blacklist');
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()->GetClientBlacklist(['id' => 42]);

if ($response['data']['blacklisted'] ?? false) {
    $reason = $response['data']['reason'];
}

Blacklisting a Client

post/api/v1/admin/clients/{id}/blacklist
Clients/CreateClientBlacklist admin can suspend services

Blacklists the client and applies the restrictions you choose.

Body 3
reasonstringrequiredThe reason. Values: payment_fraud · chargeback · abuse · spam · tos_violation · false_info · other
notesstringA free-text note explaining the decision.
restrictionsobjectWhich restrictions to switch on. Send none and none are applied; the client is only flagged.
block_new_ordersboolBlocks new orders.
block_renewalsboolBlocks renewals.
block_ticketsboolBlocks opening tickets.
suspend_servicesboolSuspends the services.
Response fields data
dataobjectThe blacklist record, returned with 201. Same shape as the status endpoint above.
Errors 4
not_found404No such client.
reason_invalid422The reason is not one of the allowed values.
blocked_by_gate422A hook on gate:user.blacklist_add vetoed the operation.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/blacklist' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"reason":"chargeback","notes":"Two chargebacks","restrictions":{"block_new_orders":true,"suspend_services":true}}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/blacklist', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({"reason":"chargeback","notes":"Two chargebacks","restrictions":{"block_new_orders":true,"suspend_services":true}}),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/blacklist');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'reason'       => 'chargeback',
        'notes'        => 'Two chargebacks',
        'restrictions' => [
            'block_new_orders' => true,
            'suspend_services' => true,
        ],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->CreateClientBlacklist([
    'id'           => 42,
    'reason'       => 'chargeback',
    'notes'        => 'Two chargebacks',
    'restrictions' => [
        'block_new_orders' => true,
        'suspend_services' => true,
    ],
]);

Removing from the Blacklist

delete/api/v1/admin/clients/{id}/blacklist
Clients/DeleteClientBlacklist admin

Removes the blacklist record. Bringing suspended services back is a separate choice.

Body 1
reactivate_servicesboolReactivates the services that this blacklisting suspended. Leave it out and they stay suspended.
Response fields data
removedboolWhether the removal succeeded.
Errors 3
not_found404No such client.
not_blacklisted422The client is not on the blacklist.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/42/blacklist' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"reactivate_services":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/blacklist', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ reactivate_services: true }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->DeleteClientBlacklist([
    'id'                  => 42,
    'reactivate_services' => true,
]);

Duplicate Account Scan

get/api/v1/admin/clients/{id}/duplicates
Clients/GetClientDuplicates admin IP · name · company

Returns other accounts that share the client's IP, name or company name.

Response fields data — 2
currentobjectA summary of the client under review: id, full_name, company_name, ip, created_at.
matchesobject[]The matching accounts.
idintId of the matching client.
full_namestringFull name.
company_namestringCompany name.
ipstringThe IP recorded at sign-up.
created_atstring | nullSign-up date.
days_apartintDays between the two sign-up dates. An absolute value.
match_typesstring[]What matched: ip, name, company.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/42/duplicates' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/42/duplicates', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();

const sameIp = body.data.matches.filter((m) => m.match_types.includes('ip'));
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/duplicates');
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()->GetClientDuplicates(['id' => 42]);

foreach ($response['data']['matches'] as $match) {
    // Same IP a few days apart is a stronger signal than a name match.
    if (in_array('ip', $match['match_types'], true) && $match['days_apart'] < 7) {
        $suspects[] = $match['id'];
    }
}
Response
{
  "data": {
    "current": {
      "id": 42,
      "full_name": "John Doe",
      "company_name": "",
      "ip": "203.0.113.10",
      "created_at": "2026-01-01 10:00:00"
    },
    "matches": [
      {
        "id": 57,
        "full_name": "J. Doe",
        "company_name": "",
        "ip": "203.0.113.10",
        "created_at": "2026-01-03 09:20:00",
        "days_apart": 2,
        "match_types": ["ip", "name"]
      }
    ]
  }
}

Pitfalls

Removing does not bring services back

Removing the blacklist record does not reactivate suspended services on its own. Send reactivate_services if you want them back; otherwise the client is off the list while the services stay suspended.

A match is not a verdict

The same IP can be a home or an office, and the same name can be a coincidence. Read match_types together with days_apart: two accounts opened from one IP days apart is a far stronger signal than two that only share a name.

The scan reads the sign-up IP

The address compared is the IP from sign-up, not the last sign-in. On a long-lived account that address can be years old.

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.