Balance

6 views Markdown

The three endpoints giving the prepaid balance, its movements and the collection settings.

Overview

The wallet is the account's prepaid balance. It can pay an invoice, and renewals can be taken from it by themselves when asked.

Three endpoints answer three questions: how much is there, where the money went and how collection should work.

The wallet has a currency of its own and it is separate from the display choice on the profile. Always read an amount together with the currency beside it.

Reference

Reading the Wallet

get/api/v1/client/balance
Balance/GetBalance always fresh

Returns the wallet, the automatic payment setting and the warning setup in one call.

Response fields data — 4
balanceobjectWhat the wallet holds. It is fresh on every read and never from a cache.
low_balanceboolWhether the balance sits under the warning threshold.
auto_payobjectWhere automatic payment stands.
from_balanceboolWhether renewals are taken from the wallet.
availableboolWhether the installation offers it. It rests on the operator's balance module.
backup_cardobjectThe card charged when the wallet falls short. It carries an id, a brand and the last four digits.
alertobjectThe low balance warning.
enabledboolWhether the warning is on.
thresholdobjectThe warning threshold. It comes in the wallet's currency.
recipientsobject[]Who gets the warning. The row numbered zero is the account owner and cannot be closed; the rest are billing addresses.
Errors 1
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/balance' \
  -H "Authorization: Bearer $CLIENT_KEY"
const res = await fetch('https://panel.example.com/api/v1/client/balance', {
  headers: { Authorization: `Bearer ${clientKey}` },
});

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

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The wallet currency can differ from the DISPLAY one on the profile; read the amount in its own.
$w = Kernel::internal('client:Balance/GetBalance', ['owner_id' => $uid])['data'];
$amount = $w['balance']['amount'];
$cur    = $w['balance']['currency'];

Reading the Wallet Movements

get/api/v1/client/balance/transactions
Balance/GetBalanceTransactions the key's owner

Returns what came into the wallet and what left it, newest first.

Query 4
pageintWhich page.
limitintRows per page. 100 at the most.
typestringThe direction filter: money in or money out.
searchstringSearches the movement note.
Response fields data[] — 6 + meta — 4
transaction_idintThe movement id.
typestringIts direction: in or out.
amountobjectThe amount and its currency.
descriptionstringThe movement note. It usually carries an invoice number.
invoice_idintThe invoice closed. It fills when the movement closed one.
created_atstringWhen the movement happened.
totalintHow many movements 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 2
type_invalid422The direction filter is neither of the two values.
insufficient_scope403The key lacks the required scope.
Request
curl 'https://panel.example.com/api/v1/client/balance/transactions?type=down&limit=50' \
  -H "Authorization: Bearer $CLIENT_KEY"
const url = new URL('https://panel.example.com/api/v1/client/balance/transactions');
url.searchParams.set('type', 'down');

const res = await fetch(url, { headers: { Authorization: `Bearer ${clientKey}` } });
const { data, meta } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/balance/transactions?type=down');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $clientKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// A movement is a HISTORICAL record: a refund adds a row the other way rather than removing the old one.
$rows = Kernel::internal('client:Balance/GetBalanceTransactions',
    ['owner_id' => $uid, 'type' => 'down'])['data'];
$paid = array_column($rows, 'invoice_id');

Changing the Wallet Settings

patch/api/v1/client/balance/settings
Balance/UpdateBalanceSettings it reaches the invoices

Sets automatic payment and the low balance warning.

Body 4
auto_pay_from_balanceboolWhether renewals are taken from the wallet. Changing it makes the open renewal invoices follow the new choice at once.
backup_card_idintThe card charged when the wallet falls short. Sending zero clears the card chain entirely.
alertobjectThe warning block. It carries an on flag and a threshold, and turning it on wants a positive threshold.
alert_recipientsint[]The billing address ids that get the warning. The list replaces, and the account owner never appears in it and always gets it.
Response fields data — 4
dataobjectThe wallet as it now stands. Same shape as the read endpoint.
Errors 6
not_found404The backup card is none of this account's saved cards.
nothing_to_update422The body holds no field that can be updated.
auto_pay_invalid422The automatic payment field is of the wrong kind.
card_expired422The backup card has expired.
alert_invalid422The warning block is broken or the threshold is missing.
insufficient_scope403The key lacks the required scope.
Request
curl -X PATCH 'https://panel.example.com/api/v1/client/balance/settings' \
  -H "Authorization: Bearer $CLIENT_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"auto_pay_from_balance":true,"alert":{"enabled":true,"threshold":50}}'
const res = await fetch('https://panel.example.com/api/v1/client/balance/settings', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${clientKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    alert: { enabled: true, threshold: 50 },
    alert_recipients: [84],
  }),
});

const { data } = await res.json();
$ch = curl_init('https://panel.example.com/api/v1/client/balance/settings');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $clientKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['auto_pay_from_balance' => true]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
// The recipient list REPLACES: read the ones already on and merge before adding an address.
$cur = Kernel::internal('client:Balance/GetBalance', ['owner_id' => $uid])['data'];
$on  = array_column(array_filter($cur['alert']['recipients'],
    fn ($r) => $r['enabled'] && ! $r['owner']), 'id');

$on[] = $newAddressId;
Kernel::internal('client:Balance/UpdateBalanceSettings',
    ['owner_id' => $uid, 'alert_recipients' => $on]);

Pitfalls

The wallet currency is not the display choice

The wallet lives in its own currency while the one on the profile says how amounts are shown. The two can differ, and reading the balance in the display currency gives a wrong figure. The warning threshold is in the wallet's currency as well.

The recipient list replaces

Writing the warning recipients overwrites the list: an address you leave out stops getting the warning. Sending only the address you meant to add closes the others. The account owner never enters this list and always gets the warning.

Changing automatic payment reaches the open invoices

Turning the wallet collection of renewals on or off also takes the renewal invoices open at that moment down the new road. This is not a setting for the future: the collections waiting are hit at once. Make sure the balance covers them.

The backup card steps in when the wallet falls short

The backup card works after the wallet rather than in place of it: the balance is tried first and the card is charged when it falls short. Sending zero clears the chain, and a failed collection can suspend a service. Saving is refused on a card that has expired.

The movements are a historical record

A wallet movement is never removed or corrected: a refund adds a new row the other way rather than taking the old one out. Read the balance from the wallet state rather than adding the movements up, since that value is fresh on every call.

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.