Returns the current loyalty token balance for the authenticated user.
| Time | Status | User Agent | |
|---|---|---|---|
Retrieving recent requests… | |||
Why this endpoint exists
On campaigns with a loyalty mechanism, the customer's balance buys down the price of their basket. This is the read for showing it: "you have €12.50 to spend" on the storefront, in the basket, and on an account page.
The balance is a ledger balance — the sum of every booked award and spend. It is not a separately stored number that can fall out of step with the entries behind it; see ledger entries for the individual movements.
Request
curl https://api.acc.funtrips.io/v2/me/loyalty/ledger/balance \
-H "x-campaign-id: $CAMPAIGN_ID" \
-H "Authorization: Bearer $END_USER_TOKEN"Response
{
"data": {
"external_user_id": "customer-4815162342",
"tokens": 1250,
"last_updated_at": "2026-09-02T09:41:07Z"
},
"meta": { "correlation_id": "…" }
}| Field | Meaning |
|---|---|
external_user_id | Your identifier for the customer |
tokens | Current balance — a bare integer with no unit |
last_updated_at | When the balance last changed |
tokens has no unit — you have to look it up
tokens has no unit — you have to look it up
1250is €12.50 when the campaign'stoken_unitiscents, and 1250 points when it ispoints.The balance carries no unit of its own. Read
token_unitfromGET /campaign/configand format accordingly.
This is the single most consequential formatting decision in the loyalty surface. Getting it wrong renders a €12.50 balance as "1250 points" — or, worse, renders 1250 points as "€12.50" and promises the customer a hundredfold discount they will not receive.
Fetch the config once at boot, keep the unit alongside your formatter, and never hardcode it: the same integration can serve both kinds of campaign.
function formatBalance(tokens, tokenUnit, currency) {
if (tokenUnit === 'cents') {
return new Intl.NumberFormat('nl-NL', { style: 'currency', currency })
.format(tokens / 100);
}
return `${tokens.toLocaleString('nl-NL')} points`;
}You do not spend the balance yourself
There is no "apply loyalty" call. When a checkout is created with an authenticated customer, the platform resolves their entitlement and applies it server-side; the discount comes back on the checkout lines as a LOYALTY entry in discounts, with the wallet as its source.
So the sequence is: show the balance from this endpoint, create the checkout with the customer's token, and render the discount the response reports. Never compute the discount client-side — the allocator decides how a balance spreads across lines, and it will not match your arithmetic.
A balance lags a fresh write
Entries created through POST /admin/loyalty/ledgers/{id}/entries
are booked asynchronously. A read immediately after that endpoint returns 202 will usually
still show the old balance.
If your flow needs to show the post-award figure right away, compute it locally from the award, or
wait for the loyalty.LedgerBalanceChanged event — do not poll this endpoint in a tight loop
expecting it to change.
Freshness
On campaigns where a partner platform owns the authoritative counter, this endpoint returns the local ledger balance, which can lag the partner's. Call POST /me/loyalty/ledger/sync to reconcile before showing a balance the customer will act on.
For campaigns where the platform owns the balance, this endpoint is authoritative and no sync is needed.
Related
- Loyalty wallet integration — the write and read sides together
POST /admin/loyalty/ledgers/{id}/entries— how tokens get here
Errors
| Status | type | Meaning |
|---|---|---|
401 | — | Token missing, expired or invalid |
404 | wallet-balance-not-found | No balance exists for this customer in this campaign |
500 | — | Unexpected fault. Retryable |
404 means "no wallet yet", not "an error"
404 means "no wallet yet", not "an error"A customer who has never earned or spent anything has no balance record. That is the normal state for a new customer on a loyalty campaign.
Render it as a zero balance, not as a failure. Falling over on a 404 here breaks the storefront for exactly the customers you most want to onboard.
const res = await fetch(`${BASE}/me/loyalty/ledger/balance`, { … });
const tokens = res.status === 404 ? 0 : (await res.json()).data.tokens;