Affiliate

7 views Markdown

The nine endpoints that open an affiliate partnership, set the commission, run the payout and scan for suspicious referrals.

Overview

In an affiliate partnership a client earns commission on every sale they bring. These endpoints open the partnership, set the commission rule, run the payout and scan for suspicious referrals.

Commission has two periods: lifetime pays on every renewal the referred client makes, onetime only on the first sale. The choice is per partner.

Reference

Reading the Affiliate Status

get/api/v1/admin/clients/{id}/affiliate
Clients/GetClientAffiliate admin

Returns the client's affiliate status, commission settings and balance.

Response fields data — 8
is_affiliateboolWhether the client is an affiliate.
activated_atdatetimeWhen the partnership was opened.
disabledboolWhether the partnership is switched off.
disabled_reasonstringWhy it was switched off.
commission_valuefloatCommission rate as a percentage. Between 0 and 100.
commission_periodstringlifetime pays on every renewal, onetime only on the first sale. An empty value uses the default.
balancefloatThe partner's earnings balance.
currency_idintCurrency id of the balance.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/42/affiliate' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/affiliate');
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()->GetClientAffiliate(['id' => 42]);

if (!($response['data']['is_affiliate'] ?? false)) {
    return;
}

Opening the Partnership

post/api/v1/admin/clients/{id}/affiliate
Clients/ActivateClientAffiliate admin

Makes the client an affiliate. On a client who already is one, it returns an error.

Body 3
commission_valuenumberCommission rate. Clamped to 0-100; a value outside that is not refused, it is trimmed.
commission_periodstringlifetime or onetime. Anything else becomes empty.
currency_idintBalance currency. List: reference/currencies.
Response fields data
dataobjectThe partnership opened, returned with 201. Same shape as the status endpoint.
Errors 4
not_found404No such client.
already_affiliate422The client is already an affiliate.
activate_failed500The partnership could not be opened.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/affiliate' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"commission_value":10,"commission_period":"lifetime","currency_id":1}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    commission_value: 10,
    commission_period: 'lifetime',
    currency_id: 1,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/affiliate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'commission_value'  => 10,
        'commission_period' => 'lifetime',
        'currency_id'       => 1,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->ActivateClientAffiliate([
    'id'                => 42,
    'commission_value'  => 10,
    'commission_period' => 'lifetime',
    'currency_id'       => 1,
]);

Updating the Partnership

patch/api/v1/admin/clients/{id}/affiliate
Clients/UpdateClientAffiliate admin blocking lives here

Changes the commission, the balance and the partner's state. Blocking goes through here too.

Body 7
commission_valuenumberCommission rate; clamped to 0-100.
commission_periodstringlifetime ya da onetime.
currency_idintBalance currency.
balancenumberThe partner's balance. A negative value is pulled up to zero.
disabledboolSwitches the partnership off or back on.
disabled_reasonstringWhy it was switched off. Only written while disabled is on.
block_partnerboolBlocks the partner and cancels the waiting withdrawal requests.
Response fields data
dataobjectThe partnership as it now stands. Same shape as the status endpoint.
Errors 3
not_found404No such client.
not_affiliate422The client is not an affiliate.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/42/affiliate' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"commission_value":15}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ commission_value: 15 }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Blocking also cancels the waiting withdrawal requests.
$response = Api::Clients()->UpdateClientAffiliate([
    'id'            => 42,
    'block_partner' => true,
    'block_reason'  => 'Fake referrals',
]);

Running a Fraud Check

post/api/v1/admin/clients/{id}/affiliate/fraud-check
Clients/CheckClientAffiliateFraud admin

Scans the partner's referrals and returns the suspicious patterns.

Body
No body is needed. The scan always covers the partner's whole referral history and cannot be narrowed.
Response fields data — 3
flaggedboolWhether the partner was flagged.
self_referralsintHow many referrals the partner made to themselves.
shared_ip_referralsarrayReferrals coming from the same IP as the partner.
Errors 3
not_found404No such client.
not_affiliate422The client is not an affiliate.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/42/affiliate/fraud-check' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate/fraud-check', {
  method: 'POST',
  headers: { Authorization: `Bearer ${apiKey}` },
});

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

// The scan only reports; blocking is your decision and your call.
if ($response['data']['flagged'] ?? false) {
    $selfReferrals = $response['data']['self_referrals'];
}

Listing Withdrawal Requests

get/api/v1/admin/clients/{id}/affiliate/withdrawals
Clients/GetClientAffiliateWithdrawals admin

Returns the requests the partner opened to withdraw their earnings.

Response fields data[] — 6
idintId of the withdrawal request.
amountfloatThe amount asked for.
gatewaystringThe method the payment goes out through.
statusstringawaiting, process, completed, rejected or cancelled.
status_msgstringA note on the status.
created_atdatetimeWhen the request was opened.
Errors 3
not_found404No such client.
not_affiliate422The client is not an affiliate.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals');
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()->GetClientAffiliateWithdrawals(['id' => 42]);

Updating a Withdrawal Request

patch/api/v1/admin/clients/{id}/affiliate/withdrawals/{wid}
Clients/UpdateClientAffiliateWithdrawal admin a receipt can be attached

Changes the request status. If you made the payment, the receipt goes in the same request.

Body 4
statusstringrequiredThe new status. Older names are accepted: pendingawaiting, inprocessprocess, paidcompleted.
status_msgstringA message for the partner.
receiptstring | objectThe payment receipt. Accepted only on the move to completed. A base64 data URI, {filename, content} or {url}. Allowed: images and PDF.
remove_receiptboolRemoves the receipt already attached.
Response fields data
dataobjectThe request as it now stands. Same shape as an item in the request list.
Errors 4
not_found404No such client or withdrawal request.
status_required422status was empty.
file_invalid422The receipt could not be read or stored.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals/5' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":"completed","receipt":{"url":"https://example.com/receipt.pdf"}}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals/5', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    status: 'completed',
    receipt: { url: 'https://example.com/receipt.pdf' },
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/affiliate/withdrawals/5');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'status'  => 'completed',
        'receipt' => ['url' => 'https://example.com/receipt.pdf'],
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->UpdateClientAffiliateWithdrawal([
    'id'      => 42,
    'wid'     => 5,
    'status'  => 'completed',
    'receipt' => ['url' => 'https://example.com/receipt.pdf'],
]);

Listing Canned Texts

get/api/v1/admin/clients/affiliate/templates
Clients/GetAffiliateTemplates admin

Returns the canned texts used in affiliate work: deactivation reasons, withdrawal notes, block reasons.

Response fields data — 3
deactivate_reasonsstring[]Reasons for closing a partnership.
withdrawal_notesstring[]Notes put on a withdrawal request.
block_reasonsstring[]Reasons for blocking.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/affiliate/templates' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/affiliate/templates', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/affiliate/templates');
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()->GetAffiliateTemplates();

Adding a Canned Text

post/api/v1/admin/clients/affiliate/templates
Clients/AddAffiliateTemplate admin

Adds a text to the type you choose.

Body 2
typestringrequiredMetnin türü: deactivate_reasons, withdrawal_notes or block_reasons.
valuestringrequiredThe text to add.
Response fields data — 2
typestringThe type the text went into.
itemsstring[]That type's list as it now stands.
Errors 3
type_invalid422The type is not one of the three values.
value_required422value was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/clients/affiliate/templates' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"block_reasons","value":"Fake referrals detected"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/affiliate/templates', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ type: 'block_reasons', value: 'Fake referrals detected' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/affiliate/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'  => 'block_reasons',
        'value' => 'Fake referrals detected',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->AddAffiliateTemplate([
    'type'  => 'block_reasons',
    'value' => 'Fake referrals detected',
]);

Deleting a Canned Text

delete/api/v1/admin/clients/affiliate/templates
Clients/DeleteAffiliateTemplate admin deleted by text

Removes a text from the list. You send the text itself, not an id.

Body 2
typestringrequiredMetnin türü: deactivate_reasons, withdrawal_notes or block_reasons.
valuestringrequiredThe text to remove. It has to match the stored one exactly.
Response fields data — 2
typestringThe type the text came out of.
itemsstring[]That type's list as it now stands.
Errors 3
type_invalid422The type is not one of the three values.
value_required422value was empty.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/clients/affiliate/templates' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"type":"block_reasons","value":"Fake referrals detected"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/affiliate/templates', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ type: 'block_reasons', value: 'Fake referrals detected' }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/affiliate/templates');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'type'  => 'block_reasons',
        'value' => 'Fake referrals detected',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->DeleteAffiliateTemplate([
    'type'  => 'block_reasons',
    'value' => 'Fake referrals detected',
]);

Pitfalls

Values are trimmed, not refused

The commission rate is clamped to 0-100, a negative balance is pulled up to zero, and an unrecognised commission period becomes empty. None of these raise an error; a wrong value is quietly stored as a corrected one. Read back after writing.

Blocking cancels waiting payouts

block_partner does not only stop the partner; it also cancels the waiting withdrawal requests. Look at the request list before blocking a partner who is about to be paid.

The fraud scan makes no decision

The scan only reports: how many self-referrals there are and which records came from the same IP. Blocking the partner is a separate request and the decision is yours.

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.