Affiliate Assignments

8 vues Markdown

The six endpoints that link, list and unlink clients and services against a partner.

Overview

A partner earns in two ways. A client link says "they brought this customer in", while a service link writes commission on one particular sale.

The two live in different places. A client link is a field on the client record, and a service link opens a commission record of its own. That is why the two removal endpoints speak different numbers.

Linking a service looks for five conditions. The program has to be on and the service type suitable. The product must stay open to commission, the partner switched on, and the service free of another partner.

Reference

Listing the Linked Clients

get/api/v1/admin/affiliates/assigned-clients
Affiliates/GetAssignedClients admin

Returns the clients linked to a partner.

Query 3
pageintWhich page.
limitintRecords per page. 100 at the most.
searchstringSearches the client and partner name along with the company.
Response fields data[] — 8 + meta — 4
idintThe client id.
full_namestringThe client's name.
company_namestringThe client's company.
emailstringThe client's e-mail.
aff_idintThe client number of the partner they are linked to. Not the partner record id.
aff_namestringThe partner's name.
aff_companystringThe partner's company.
aff_user_idintThe partner's client id.
totalintHow many linked clients there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/affiliates/assigned-clients?search=jane' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/affiliates/assigned-clients', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data } = await res.json();
const perPartner = Object.groupBy(data, (c) => c.aff_user_id);
$ch = curl_init('https://panel.example.com/api/v1/admin/affiliates/assigned-clients');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// aff_id is the partner's CLIENT number; do not try to match it with the partner record id.
$rows = Api::Affiliates()->GetAssignedClients()['data'];
$mine = array_filter($rows, fn ($c) => (int) $c['aff_user_id'] === $partnerClientId);

Linking a Client to a Partner

post/api/v1/admin/affiliates/assigned-clients
Affiliates/AssignClient admin

Marks a client as a partner's referral.

Body 2
affiliate_idintreqThe partner record id.
client_idintreqThe id of the client to link.
Response fields data — 3
assignedboolWhether the link was made.
affiliate_idintThe partner id.
client_idintThe client id.
Errors 4
missing_params422The partner or client id is missing.
assign_self422A client cannot be linked to their own partnership.
not_found404No such partner or client.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/affiliates/assigned-clients' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"affiliate_id":7,"client_id":88}'
const res = await fetch('https://panel.example.com/api/v1/admin/affiliates/assigned-clients', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ affiliate_id: 7, client_id: 88 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/affiliates/assigned-clients');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['affiliate_id' => 7, 'client_id' => 88]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A client tied to another partner is taken over SILENTLY; the first partner loses the referral.
$rows = Api::Affiliates()->GetAssignedClients()['data'];
$owned = array_column($rows, 'aff_user_id', 'id');
if (! isset($owned[$clientId])) Api::Affiliates()->AssignClient(['affiliate_id' => $aid, 'client_id' => $clientId]);

Removing a Client Link

delete/api/v1/admin/affiliates/assigned-clients
Affiliates/UnassignClients admin

Takes the partner link off one client or more.

Body 1
idsint[]reqThe client ids. Not assignment ids.
Response fields data — 1
unassignedint[]The client ids you sent.
Errors 2
ids_required422No client id was given.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/affiliates/assigned-clients' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"ids":[88,90]}'
const res = await fetch('https://panel.example.com/api/v1/admin/affiliates/assigned-clients', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ids: [88, 90] }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// There is NO check: the link goes off every client you send, and unlinked ones fill the answer too.
Api::Affiliates()->UnassignClients(['ids' => [88, 90]]);

Listing the Linked Services

get/api/v1/admin/affiliates/assigned-services
Affiliates/GetAssignedServices admin

Returns the service records earning partners their commission.

Query 4
pageintWhich page.
limitintRecords per page. 100 at the most.
searchstringSearches the client, partner, service name and record id.
statusstringThe record status filter.
Response fields data[] — 15 + meta — 4
idintThe commission record id. The removal endpoint wants this number.
affiliate_idintThe partner record id.
service_idintThe service id.
service_namestringThe service name.
user_idintThe id of the client owning the service.
full_namestringThe client's name.
company_namestringThe client's company.
amountstringThe service amount. What the commission is worked out from.
currencyintThe currency id of the amount.
commissionstringThe commission written to the partner.
statusstringWhere the record stands. A completed one already went to the balance.
aff_namestringThe partner's name.
aff_companystringThe partner's company.
aff_user_idintThe partner's client id.
ctimestringWhen the record was opened.
totalintHow many records there are. It comes back under meta.
pageintThe page you are on.
limitintThe page size.
next_pageintThe next page. Zero on the last one.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/affiliates/assigned-services?limit=50' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch('https://panel.example.com/api/v1/admin/affiliates/assigned-services', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data } = await res.json();
const earned = data.reduce((s, t) => s + Number(t.commission), 0);
$ch = curl_init('https://panel.example.com/api/v1/admin/affiliates/assigned-services');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The removal endpoint wants the RECORD id from here rather than the SERVICE id.
$rows = Api::Affiliates()->GetAssignedServices()['data'];
$recordId = array_column($rows, 'id', 'service_id')[$serviceId] ?? 0;

Linking a Service to a Partner

post/api/v1/admin/affiliates/assigned-services
Affiliates/AssignService admin five conditions

Links a service to a partner and opens the commission record.

Body 2
affiliate_idintreqThe partner record id.
service_idintreqThe id of the service to link.
Response fields data — 3
assignedboolWhether the link was made.
affiliate_idintThe partner id.
service_idintThe service id.
Errors 8
missing_params422The partner or service id is missing.
system_disabled422The affiliate program is off.
type_unsupported422The service type does not take commission. Hosting, server, software and special types pass.
product_disabled422The affiliate program is off for this product.
affiliate_disabled422The partner is off.
already_assigned422The service is linked to another partner.
not_found404No such partner or service.
insufficient_scope403The key lacks the required scope.
Request
curl -X POST 'https://panel.example.com/api/v1/admin/affiliates/assigned-services' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"affiliate_id":7,"service_id":305}'
const res = await fetch('https://panel.example.com/api/v1/admin/affiliates/assigned-services', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ affiliate_id: 7, service_id: 305 }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/affiliates/assigned-services');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['affiliate_id' => 7, 'service_id' => 305]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// Five conditions are checked at once; the two missed most are an off program and a service already linked.
try     { Api::Affiliates()->AssignService(['affiliate_id' => $aid, 'service_id' => $sid]); }
catch   (\Throwable $e) { $skipped[$sid] = $e->getMessage(); }

Removing a Service Link

delete/api/v1/admin/affiliates/assigned-services/{tid}
Affiliates/UnassignService admin touches the balance

Removes the commission record and takes the commission back where needed.

Response fields data — 2
unassignedboolWhether the removal ran.
idintThe id of the record removed.
Errors 2
not_found404No such commission record.
insufficient_scope403The key lacks the required scope.
Request
curl -X DELETE 'https://panel.example.com/api/v1/admin/affiliates/assigned-services/12' \
  -H "Authorization: Bearer $API_KEY"
const res = await fetch(`https://panel.example.com/api/v1/admin/affiliates/assigned-services/${tid}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/affiliates/assigned-services/' . $tid);
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);
// On a completed record the commission comes OFF the balance; the OPPOSITE way to a payout removal.
Api::Affiliates()->UnassignService(['tid' => $recordId]);

Pitfalls

The two removal endpoints want two different numbers

The endpoint removing a client link wants client ids in the body. The one removing a service link wants the commission record id in the address. Handing the second a service id either answers not found or removes the wrong record. Take the record id from the listing endpoint.

Removing a client link checks nothing

This endpoint takes the link off every client id you send. It never looks at whether the id was linked to a partner, nor whether that client exists. All of them count as removed in the answer. A wrong list quietly cuts other clients' links.

A link silently takes over an existing one

When a client is already linked to another partner the new link raises no error. It writes over the old one, and the first partner loses that referral. On the service side the same case is refused with already_assigned. Check the existing link from the listing endpoint before linking a client.

Removing a commission record takes from the balance

Removing a completed commission record takes the amount off the partner's balance. Remember that removing a payout request adds to it: the two removals work in opposite directions. The balance never goes below zero, so a difference that cannot be taken disappears without a word.

The link field holds a client id and not a partner id

The aff_id in the listing is the client behind the partner and not the partner record id. The linking endpoint, meanwhile, wants the partner record id in the body. Passing a number read from one listing straight into the other assigns to a different partner.

The service type and product setting form a quiet gate

Commission is written on hosting, server, software and special services alone, and other types such as a domain get type_unsupported. The product's own setting can close commission as well. Gather those two cases apart during bulk linking work.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.