Affiliate

6 views Markdown

The five endpoints managing a partner's programme, earnings and payout requests.

Overview

The affiliate programme is where a customer takes a share of the business their own link brings in. These five endpoints manage one partner's own programme.

Earnings move in two stages: a commission clears first and then reaches the available balance. A payout is asked from the available amount alone.

Payouts are settled by hand. A request is opened, the operator approves it, and only one request stands open at a time.

Reference

Reading Where You Stand

get/api/v1/client/affiliate
Affiliate/GetAffiliate it moves what cleared

Returns the programme, the earnings and the payout state in one call.

Response fields data — 7
enrolledboolWhether the account joined.
disabledboolWhether the operator closed this partnership.
programobjectThe programme rules. The commission rate, the tracking window, the clearing period and the payout floor.
referralobjectThe referral to share. It carries a code and a full link.
balanceobjectThe earnings block.
availableobjectWhat can be asked for now.
clearingobjectWhat is waiting to clear.
earnedobjectWhat has been earned in all.
paid_outobjectWhat has been paid out.
statsobjectThe referral counts. Those who signed up and those who earned commission.
payoutobjectWhere payout stands.
readyboolWhether what is available reaches the floor.
pending_requestobjectThe one request waiting. It carries an amount, a method, a state and when it was asked.
gatewaysstring[]The payout roads the operator offers.
saved_destinationsobjectYour saved destination per road.
Errors 2
affiliate_disabled422The operator closed the affiliate programme.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/affiliate' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/affiliate', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
if (! data.enrolled) showJoinButton(data.program);
$ch = curl_init('https://panel.example.com/api/v1/client/affiliate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// This read WRITES: it moves the cleared commissions into the available balance and is not read-only.
$aff = Kernel::internal('client:Affiliate/GetAffiliate', ['owner_id' => $uid])['data'];
$canAsk = $aff['payout']['ready'] ?? false;

Joining the Programme

post/api/v1/client/affiliate/enroll
Affiliate/EnrollAffiliate the account currency

Signs the account up to the affiliate programme.

Body
No body is needed.
Response fields 201 — data — 7
dataobjectThe new partnership dashboard. Same shape as the read endpoint.
Errors 2
affiliate_disabled422The operator closed the affiliate programme.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/client/affiliate/enroll' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/affiliate/enroll', {
  method: 'POST',
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
shareLink(data.referral.link);
$ch = curl_init('https://panel.example.com/api/v1/client/affiliate/enroll');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The partnership opens in THE ACCOUNT's currency and cannot be changed later; check the profile first.
$me = Kernel::internal('client:Account/GetMe', ['owner_id' => $uid])['data'];
if ($me['currency'] === $wanted)
    Kernel::internal('client:Affiliate/EnrollAffiliate', ['owner_id' => $uid]);

Listing the Commissions

get/api/v1/client/affiliate/commissions
Affiliate/GetAffiliateCommissions the key's owner

Returns the commissions earned, newest first.

Query 2
pageintWhich page.
limitintRows per page. 100 at the most.
Response fields data[] — 10 + meta — 4
commission_idintThe record id.
statusstringThe raw state.
statestringThe state shown: available, clearing or refused.
referralstringThe display name of the customer referred.
servicestringThe service the commission came from.
service_typestringThe service's product type.
order_amountobjectThe amount of the order referred.
commissionobjectYour share of it.
created_atstringWhen the record was written.
clearing_datestringThe day it becomes available.
totalintHow many records there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page.
Errors 3
affiliate_disabled422The operator closed the affiliate programme.
not_enrolled422The account has not joined the programme.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/affiliate/commissions' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/affiliate/commissions', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

const { data } = await res.json();
const soon = data.filter((c) => c.state === 'clearing');
$ch = curl_init('https://panel.example.com/api/v1/client/affiliate/commissions');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A clearing commission CANNOT be paid yet: it reaches the available balance on its clearing day.
$rows = Kernel::internal('client:Affiliate/GetAffiliateCommissions', ['owner_id' => $uid])['data'];
$soon = array_filter($rows, fn ($c) => $c['state'] === 'clearing');

Asking to Be Paid

post/api/v1/client/affiliate/withdraw
Affiliate/WithdrawAffiliate one request at a time

Asks for the available earnings to be paid out.

Body 4
amountfloatreqThe amount asked for. It has to sit between the floor and what is available.
gatewaystringreqThe payout road. It has to be one the operator offers.
gateway_infostringThe destination detail. It can be left out where one is saved.
save_defaultboolKeep this destination for later requests.
Response fields 201 — data — 4
withdrawal_idintThe request id.
amountobjectThe amount asked for.
gatewaystringThe road picked.
statusstringWhere the request stands. The operator settles it by hand.
Errors 10
affiliate_disabled422The operator closed the affiliate programme.
not_enrolled422The account has not joined the programme.
affiliate_disabled_account422The operator closed this partnership.
withdrawal_pending422A request is already waiting.
below_minimum422What is available does not reach the floor.
amount_invalid422The amount sits outside the range allowed. Both bounds come in the answer's detail.
gateway_invalid422The road is none of the operator's.
gateway_info_required422No destination was given and none is saved.
withdrawal_rejected422A hook refused the request.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/client/affiliate/withdraw' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"amount":40,"gateway":"PayPal","gateway_info":"[email protected]"}'
const res = await fetch('https://panel.example.com/api/v1/client/affiliate/withdraw', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ amount, gateway, save_default: true }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/affiliate/withdraw');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['amount' => 40, 'gateway' => 'PayPal']),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Take the amount FROM THE DASHBOARD: what is available can move as commissions clear during the read.
$aff = Kernel::internal('client:Affiliate/GetAffiliate', ['owner_id' => $uid])['data'];
$max = $aff['balance']['available']['amount'];

Kernel::internal('client:Affiliate/WithdrawAffiliate',
    ['owner_id' => $uid, 'amount' => $max, 'gateway' => $gw]);

Saving a Payout Destination

put/api/v1/client/affiliate/payment-method
Affiliate/SaveAffiliatePayoutMethod the key's owner

Saves or clears the destination detail of a payout road.

Body 2
gatewaystringreqThe payout road.
infostringThe destination detail. Sending it empty clears the record.
Response fields data — 1
saved_destinationsobjectThe destination map as it now stands. It carries the detail saved per road.
Errors 4
affiliate_disabled422The operator closed the affiliate programme.
not_enrolled422The account has not joined the programme.
gateway_invalid422The road is none of the operator's.
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/client/affiliate/payment-method' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"gateway":"PayPal","info":"[email protected]"}'
const res = await fetch('https://panel.example.com/api/v1/client/affiliate/payment-method', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ gateway, info }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/affiliate/payment-method');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['gateway' => $gw, 'info' => $info]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A destination is kept PER ROAD: clearing one leaves the others alone and the map is managed one by one.
Kernel::internal('client:Affiliate/SaveAffiliatePayoutMethod',
    ['owner_id' => $uid, 'gateway' => $gw, 'info' => '']);

Pitfalls

Reading the dashboard actually writes

Reading where you stand moves the commissions whose clearing day arrived into the available balance. The endpoint is not read-only, and two calls in a row can show different balances. Call it right before a payout request to see the highest amount you may ask for.

A clearing commission is not money yet

A commission is not paid the moment it is earned: it waits out the clearing period the operator set. The clearing figure on the dashboard is part of what was earned and cannot be asked for. Adding the two figures and asking for the sum is refused.

One payout request at a time

A second request cannot be opened while one waits. A new one is refused until the operator settles it by hand. Read the pending field on the dashboard and close the request road in your interface, or the customer meets an error they cannot place.

The partnership opens in the account currency

The partnership record opens in the account's currency at the time of joining and the commissions build up in it. Changing the currency on the profile later does not move the earnings. Make sure the currency is right before joining.

The destination is kept per road

Every payout road keeps a destination of its own, and clearing one leaves the others alone. Leaving the destination out of a request falls back to that road's saved detail, and the request is refused where none is saved.

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.