Reseller

657 переглядів Markdown

The five endpoints that manage a client's reseller status and the program's installation-wide settings.

Overview

A reseller partnership is a client buying from you at a discount and selling on to their own customers. These endpoints run two separate layers that should not be confused.

The first three look at one client: their reseller status, credit threshold and discount tiers. The last two run the program itself: how applications are approved, whether verification is required, whether resellers get API access. Whether the path carries a client id tells you which layer you are on.

Structure

Discount Tiers

The per-client discounts and the program-wide discount_rates share one shape. Each tier key maps to a list of rows with from, to and rate.

Tier keyApplies toExample
defaultEvery product, domain and add-ondefault
<group>A whole group: hosting, server, software, ssl, domain (every extension) or addon (every add-on)hosting
special/<group_id>A special product groupspecial/446
<group>/<category_id>A product category, including the products in its sub-categorieshosting/550
<group>/0The group's products filed under no categoryhosting/0
special-<group_id>/0The uncategorized products of one special group; special/0 covers every special groupspecial-445/0
<type>-<product_id>A single product; a special-group product is special-<product_id>hosting-15
domain-<tld>A single domain extensiondomain-com
addon-<addon_id>A single add-onaddon-8

Keys written by earlier versions still resolve. special-<group_id> is read as a special group when no special-group product has that id. domain-<tld_id> is read as the extension's numeric id. A key that matches none of the formats is stored as sent but never applies to a line. A row whose three values are all 0 is dropped on save.

Which tiers a reseller gets. The program-wide discount_rates apply to every active reseller. A key the reseller has tiers for replaces the program-wide tiers of that key as a whole. The two row lists are not merged. Every other program-wide key still applies, so a reseller with no tiers of their own is priced by the program-wide rates.

Which tier prices a line. The narrowest target with a matching row wins: product, add-on, domain extension, category, uncategorized, group, then default. For a category, the product's own category comes first, then each parent up the tree. Within a key, the first row whose range contains the count applies. A key with no matching row, or whose row has rate 0, falls through to the next target.

What is counted. A tier is chosen by the services the client holds at that tier's level. A product tier counts that product, and a category tier counts the category with its sub-categories. An uncategorized tier counts the group's uncategorized products, and a group tier the whole group. The addon group counts the client's add-ons, and default counts every service. A domain tier counts that extension, and an add-on tier that add-on. Orders, the cart and the reseller dashboard count every service that is not cancelled, pending ones included. Invoice lines count active services only: renewals, upgrades and lines written on the invoice screens.

Reference

Reading the Reseller Status

get/api/v1/admin/clients/{id}/reseller
Clients/GetClientReseller admin two shapes

Returns the client's reseller status and the settings that belong to them.

Response fields data — 7
is_resellerboolWhether the partnership is live. With no reseller record at all, this is the only field in the response.
statusstringactive or inactive.
activation_timedatetime | nullWhen it first went live.
min_creditobjectThe credit needed to buy.
amountfloatThe amount.
currency_idintCurrency id.
min_discountobjectThe balance needed for the discount to apply.
amountfloatThe amount.
currency_idintCurrency id.
only_credit_paymentboolWhether paying by credit is the only option.
discountsobjectA map from tier key (default, hosting, hosting/550, hosting-15, domain-com, addon-8) to discount tiers.
fromintLower bound of the active-service count, inclusive.
tointUpper bound, inclusive; 0 means no upper limit.
ratefloatDiscount percentage, rounded to 2 decimals.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/42/reseller' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/42/reseller', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();

// With no reseller record the response holds a single field.
if (!body.data.is_reseller) return;
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/42/reseller');
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()->GetClientReseller(['id' => 42]);

// The other fields only arrive when a record exists - read them null-safe.
$minCredit = $response['data']['min_credit']['amount'] ?? 0;
Response
{
  "data": {
    "is_reseller": true,
    "status": "active",
    "activation_time": "2026-02-01 12:00:00",
    "min_credit": { "amount": 100.00, "currency_id": 840 },
    "min_discount": { "amount": 50.00, "currency_id": 840 },
    "only_credit_payment": false,
    "discounts": {
      "hosting": [{ "from": 1, "to": 10, "rate": 15.0 }, { "from": 11, "to": 0, "rate": 17.5 }]
    }
  }
}
{
  "data": {
    "is_reseller": false
  }
}

Saving the Reseller Settings

patch/api/v1/admin/clients/{id}/reseller
Clients/UpdateClientReseller admin notifies on first activation

Saves the reseller settings that belong to the client and switches the partnership on or off.

Body 5
activeboolSwitches the partnership on or off.
min_creditobjectThe minimum credit requirement. Only meaningful while the partnership is on.
amountfloatThe amount.
currency_idintCurrency id.
min_discountobjectThe minimum balance for the discount. Only meaningful while the partnership is on.
amountfloatThe amount.
currency_idintCurrency id.
only_credit_paymentboolLimits payment to credit.
discountsobjectA map from tier key (default, hosting, hosting/550, hosting-15, domain-com, addon-8) to discount tiers.
fromintLower bound of the active-service count, inclusive.
tointUpper bound, inclusive; 0 or empty means no upper limit.
ratefloatDiscount percentage, rounded to 2 decimals.
Response fields data
dataobjectThe reseller record after the save. Same shape as the read endpoint.
Errors 2
not_found404No such client.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/admin/clients/42/reseller' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"active":true,"min_credit":{"amount":100,"currency_id":840},"only_credit_payment":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/reseller', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    active: true,
    min_credit: { amount: 100, currency_id: 840 },
    only_credit_payment: true,
    discounts: {
      5: [{ from: 1, to: 10, rate: 15 }],
    },
  }),
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// While the partnership is on, a setting you omit REMOVES that limit - read the current state first.
$current = Api::Clients()->GetClientReseller(['id' => 42])['data'];

$response = Api::Clients()->UpdateClientReseller([
    'id'                  => 42,
    'active'              => true,
    'min_credit'          => $current['min_credit'],
    'min_discount'        => $current['min_discount'],
    'only_credit_payment' => true,
    'discounts'           => $current['discounts'],
]);

Changing the Reseller Status

put/api/v1/admin/clients/{id}/reseller/status
Clients/SetClientResellerStatus admin leaves settings alone

Switches an existing reseller record on or off. The settings stay as they are.

Body 1
actionstringrequiredactivate or terminate.
Response fields data — 2
statusstringThe status afterwards.
is_resellerboolWhether the partnership is live.
Errors 3
not_found404No such client.
action_invalid422action is neither of the two values.
not_reseller422The client has no reseller record. Create one with the settings endpoint first.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/clients/42/reseller/status' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"action":"activate"}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/42/reseller/status', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ action: 'activate' }),
});

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

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

Reading the Program Settings

get/api/v1/admin/clients/reseller/config
Clients/GetResellerConfig admin installation-wide

Returns the installation-wide settings of the reseller program. It looks at no single client.

Response fields data — 10
enabledboolWhether the program is on.
activationstringHow applications are approved. The stored value is manuel or auto.
allow_non_membersboolWhether non-members can see the reseller storefront.
api_accessboolWhether resellers get API access.
payment_restrictionboolWhether payment is limited to credit.
verificationboolWhether applications must be verified.
verification_methodsstring[]The verification methods required.
min_creditobjectThe minimum credit amount.
amountfloatThe amount.
currency_idintCurrency id.
credit_thresholdobjectThe credit threshold.
amountfloatThe amount.
currency_idintCurrency id.
discount_ratesobjectA map from tier key (default, hosting, hosting/550, hosting-15, domain-com, addon-8) to discount tiers.
fromintLower bound of the active-service count, inclusive.
tointUpper bound, inclusive; 0 means no upper limit.
ratefloatDiscount percentage, rounded to 2 decimals.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/admin/clients/reseller/config' \
  -H "Authorization: Bearer $API_KEY"
const res  = await fetch('https://panel.example.com/api/v1/admin/clients/reseller/config', {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/reseller/config');
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()->GetResellerConfig();

Saving the Program Settings

put/api/v1/admin/clients/reseller/config
Clients/SaveResellerConfig admin installation-wide

Saves the installation-wide settings of the reseller program.

Body 10
enabledboolTurns the program on or off.
activationstringmanual, automatic or auto. An unrecognised value falls back to manual approval.
allow_non_membersboolOpens the storefront to non-members.
api_accessboolGives resellers API access.
payment_restrictionboolLimits payment to credit.
verificationboolMakes verification mandatory on application.
verification_methodsstring[]The verification methods to require.
min_creditobjectThe minimum credit amount.
amountfloatThe amount.
currency_idintCurrency id.
credit_thresholdobjectThe credit threshold.
amountfloatThe amount.
currency_idintCurrency id.
discount_ratesobjectA map from tier key (default, hosting, hosting/550, hosting-15, domain-com, addon-8) to discount tiers. Send the complete set; tiers left out are removed.
fromintLower bound of the active-service count, inclusive.
tointUpper bound, inclusive; 0 or empty means no upper limit.
ratefloatDiscount percentage, rounded to 2 decimals.
Response fields data
dataobjectThe program settings after the save. Same shape as the read endpoint.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl -X PUT 'https://panel.example.com/api/v1/admin/clients/reseller/config' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"enabled":true,"activation":"manual","api_access":true}'
const res = await fetch('https://panel.example.com/api/v1/admin/clients/reseller/config', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    enabled: true,
    activation: 'manual',
    api_access: true,
  }),
});

const body = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/admin/clients/reseller/config');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'enabled'    => true,
        'activation' => 'manual',
        'api_access' => true,
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
$response = Api::Clients()->SaveResellerConfig([
    'enabled'    => true,
    'activation' => 'manual',
    'api_access' => true,
]);

Pitfalls

A setting you omit removes the limit

Saving settings while the partnership is on drops any limit you did not send; the old value is not kept. Forgetting the credit threshold while you only meant to change the payment restriction removes that threshold. Read the current settings, edit them, and send all of it back.

The response shape depends on the record

With no reseller record the status endpoint returns only is_reseller and the other fields are absent. Read them null-safe rather than directly, or every non-reseller client will break your code.

The status endpoint cannot create a partnership

The quick toggle only changes an existing record; on a client without one it returns not_reseller. Opening a new partnership is the settings endpoint's job.

An upper bound of 0 is the open-ended tier

A tier whose to is 0 (or sent empty) has no upper limit: it covers from and above. Do not read it as an empty range. Tier keys are strings such as hosting or hosting-15; a bare number matches no product.

Ця стаття була корисною?

Дякуємо за відгук!

Досі потрібна допомога?

Наша служба підтримки на зв’язку цілодобово з усього, чого ви не знайшли вище.