Affiliate

1k مشاهدات 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

Compares the IP addresses the partner signed in from with the sign-in IPs of the clients they referred, and lists every referred client that shares one. Staff "log in as client" sessions are ignored. Read state first: no_referrals and no_logins mean there was nothing to compare, which is not a clean result.

Body —
——No body is needed. The scan always covers the partner's whole referral history and cannot be narrowed.
Response fields data — 7
statestringmatch · clean · no_referrals · no_logins. The last two mean the partner has no referred clients, or no sign-in of their own to compare.
has_issuesbooltrue only when state is match.
referral_countintHow many clients the partner referred.
matching_ipsarrayOne entry per shared IP and referred client; empty unless state is match.
matching_ips[].ipstringThe IP both the partner and the referred client signed in from.
matching_ips[].referral_user_idintID of the referred client.
matching_ips[].referral_namestringName of the referred client (company name when the full name is empty).
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']['state'] ?? '') === 'match') {
    $sharedIps = $response['data']['matching_ips'];
}

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: pending → awaiting, inprocess → process, paid → completed.
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.

هل كان هذا مفيدًا؟

شكرًا على ملاحظاتك!

هل ما زلت بحاجة إلى مساعدة؟

فريق الدعم متاح على مدار الساعة لمساعدتك في كل ما لم تجده أعلاه.