Overview
If you bill customers with prepaid credits on Chargebee Billing, you often want to give them visibility into those credits inside your own product—how many are left, where they came from, when they expire, and what was recently consumed.
This tutorial explains how to build those credit widgets using three Chargebee Billing read APIs: List Ledger Account Balances, List Grant Blocks, and List Ledger Operations. You don't need a special portal API for this.
This is about your customer-facing UI, not Chargebee Billing's hosted self-serve portal. All Chargebee API calls must go through your backend.
Scope
This tutorial covers three use cases:
- Total credit balance: a headline "credits remaining" view, broken down into available and on-hold.
- Grant-level breakdown: per-grant detail: how much was granted, consumed, remaining, and when it expires.
- Recent transactions: a chronological ledger of credit activity, with support for your own metadata.
It does not cover aggregated or time-series views (for example, a daily credit-usage chart), server-side filtering by metadata, or a single granted-vs-remaining rollup across all grants.
Architecture overview
The flow splits responsibilities between your backend and Chargebee Billing as follows.
Your application:
- Authenticates the signed-in customer and resolves their Chargebee
subscription_idand creditunit_id - Calls Chargebee Billing from your backend (never from the browser)
- Shapes API responses into UI-ready payloads
- Renders balance, grant, and transaction widgets in your product
Chargebee Billing:
- Returns the real-time rolled-up credit balance for a subscription and unit
- Returns grant blocks with granted, used, remaining, and expiry details
- Returns ledger operations that power a chronological activity feed
A typical flow:
- When the customer opens your portal or credits page, your front end calls your backend.
- Your backend fetches the headline balance from List Ledger Account Balances.
- Your backend fetches grant blocks for the per-grant breakdown.
- Your backend fetches recent ledger operations for the activity feed.
- Your backend returns shaped JSON; your front end renders the widgets.
Before you start
For this tutorial to work, you need the following setup:
Set up your development environment
- Obtain an API key from your Chargebee Billing test site (subdomain ends in
-test). - Have a backend service that can call Chargebee REST APIs securely.
- Be familiar with REST/JSON and the front-end framework of your choice. Formatting and layout are yours—this tutorial focuses on the data.
Set up prepaid credits in Chargebee Billing
- Configure prepaid credits on your Chargebee Billing site (credit unit, grants on plan/addon, and optional overage).
- Have at least one subscription that has already received credit grants.
- Note the subscription ID and credit unit ID (for example,
ai_credits) you will use in API filters.
APIs at a glance
| Use case | API | What it gives you |
|---|---|---|
| 1. Total balance | GET /ledger_account_balances | One rolled-up balance (provisioned + overdraft) per credit unit |
| 2. Grant breakdown | GET /grant_blocks | One row per grant block, with lifecycle detail |
| 3. Recent transactions | GET /ledger_operations | One row per credit operation, newest first |
Implementation steps
Follow these steps to power credit widgets in your customer portal.
Security
- Never expose Chargebee API keys in client-side code.
- Call Chargebee REST APIs only from secure backend services.
Conventions used in the examples below:
- Base URL:
https://YOUR_CHARGEBEE_SUBDOMAIN.chargebee.com/api/v2/ - Auth: HTTP Basic, with the API key as the username:
-u YOUR_API_KEY: - Numbers are decimal strings. Credit amounts (
balance,amount,usable_balance, and similar) are returned as strings to preserve precision. Keep them as strings (or use a decimal library) through formatting and arithmetic—don't convert with JavaScriptNumber. - List envelope. List responses use Chargebee's standard shape:
{ "list": [{ "<resource>": { ... } }], "next_offset": "..." }. Response snippets below show the inner resource object unless noted.
Use case 1: Total credit balance
Goal: a headline number—"You have X credits available"—optionally split into available vs. on-hold.
Call List Ledger Account Balances, filtered to one subscription and one credit unit. With both filters set, you typically get a single real-time balance snapshot (a provisioned/overdraft pair).
curl https://YOUR_CHARGEBEE_SUBDOMAIN.chargebee.com/api/v2/ledger_account_balances -G \
-u YOUR_API_KEY: \
--data-urlencode "subscription_id[is]=1mGETgZVF2umUZq" \
--data-urlencode "unit_id[is]=ai_credits"
Relevant fields from list[0].ledger_account_balance:
{
"provisioned_balance": {
"total_balance": "40",
"usable_balance": "36.4",
"hold_amount": "3.6"
},
"overdraft_balance": {
"is_unlimited": false,
"limit": "50",
"usable_balance": "25"
}
}
Use the response fields as follows to render the balance widget:
- Available now →
provisioned_balance.usable_balance - On hold (reserved by in-progress authorizations) →
provisioned_balance.hold_amount - Total remaining →
provisioned_balance.total_balance(usable_balance + hold_amount) - Overdraft (only if you offer it) →
overdraft_balance.usable_balance, orlimitfor the cap. Ifis_unlimitedistrue, treat overdraft as uncapped and expectlimit/ balance fields to benull.
A minimal backend handler that shapes this for your front end:
// GET /credits/balance?subscriptionId=...&unitId=...
app.get("/credits/balance", async (req, res) => {
const cb = await fetchChargebee("/ledger_account_balances", {
"subscription_id[is]": req.query.subscriptionId,
"unit_id[is]": req.query.unitId,
});
const row = cb.list?.[0]?.ledger_account_balance;
if (!row?.provisioned_balance) {
return res.json({ available: "0", onHold: "0", total: "0" });
}
const b = row.provisioned_balance;
res.json({
available: b.usable_balance,
onHold: b.hold_amount,
total: b.total_balance,
});
});
Best practice
Use List Ledger Account Balances for the headline total. Don't compute it by summing grant blocks—grants expire independently and that list is paginated, so a client-side sum can under- or over-count. Also avoid a "used X of Y granted" figure at the account level; the original granted total isn't part of this snapshot. Keep that detail at the grant level (Use case 2).
Use case 2: Grant-level breakdown
Goal: for each bucket of credits, show how much was granted, how much is left, and when it expires.
Call List Grant Blocks, filtered to the subscription (and preferably the same unit_id), scoped to account_type[is]=provisioned so overdraft blocks stay out of the prepaid breakdown, and sorted newest-first.
curl https://YOUR_CHARGEBEE_SUBDOMAIN.chargebee.com/api/v2/grant_blocks -G \
-u YOUR_API_KEY: \
--data-urlencode "subscription_id[is]=1mGETgZVF2umUZq" \
--data-urlencode "unit_id[is]=ai_credits" \
--data-urlencode "account_type[is]=provisioned" \
--data-urlencode "sort_by[desc]=created_at"
Each list[].grant_block looks like this:
{
"id": "__dev__KyVkYhVBJgyRF9",
"granted_amount": "100",
"balance": "72.5",
"hold_amount": "5",
"used_amount": "22.5",
"expires_at": 1775402925,
"grant_source": "subscription_created",
"status": "available"
}
Map each grant block to a row in your UI:
- Granted →
granted_amount - Remaining →
balance - On hold →
hold_amount(optional; useful if you show reserved credits) - Used →
used_amount - Expires →
expires_at(Unix seconds; format as a date) - Type / source →
grant_source(see below) - State →
status(available,in_grace_period,exhausted,scheduled)
Because each grant block carries its own granted_amount and used_amount, a "used vs. granted" bar or percentage is meaningful here—unlike at the account level.
Label grant sources
grant_source is the raw API value describing how the credits were issued. Treat it as a contract, not a display string—rename or group it to fit your product. A common mapping:
grant_source | Suggested label |
|---|---|
subscription_created | Initial credits (subscription created) |
subscription_renewed | Subscription renewed |
grant_renewal | Recurring / renewal credits |
subscription_changed | Plan change |
top_up | Top-up |
promotional_grants | Bonus credits |
rollover | Rolled over |
Whether you show each grant as its own row or roll several sources into one line is your call.
Watch pagination
This endpoint returns 10 grants per page by default, 100 maximum. If a subscription can accumulate more than 100 grants, page with next_offset. There is no server-side status filter—if you want only currently usable grants, filter client-side to status = available. Grants in in_grace_period are not generally usable for new consumption; if you surface them, label them separately (for example, “Expired — grace period”).
Use case 3: Recent transactions
Goal: a chronological feed of credit activity—"used 15 credits", "50 credits added", "10 expired".
Call List Ledger Operations, filtered to the subscription, newest-first, with a page size that fits your UI.
curl https://YOUR_CHARGEBEE_SUBDOMAIN.chargebee.com/api/v2/ledger_operations -G \
-u YOUR_API_KEY: \
--data-urlencode "subscription_id[is]=1mGETgZVF2umUZq" \
--data-urlencode "unit_id[is]=ai_credits" \
--data-urlencode "sort_by[desc]=created_at" \
--data-urlencode "limit=25"
To restrict to a time window, add a created_at filter (for example, created_at[after]=<unix_seconds>).
Each list[].ledger_operation is one consolidated event—even if it drew credits from several grant blocks, the customer sees a single line.
{
"id": "9lfj6x1f5",
"type": "capture",
"amount": "15",
"ledger_operation_timestamp": 1774978580,
"metadata": { "project_id": "proj_42", "user": "alice" }
}
Map each operation to a row in your UI:
- When →
ledger_operation_timestamp(business event time) orcreated_at(when Chargebee Billing recorded it) - What happened →
type, mapped to a friendly label - How many credits →
amount
Suggested type labels:
type | Suggested label | Customer-facing? |
|---|---|---|
capture, capture_authorization | Usage | Usually yes |
allocation | Credits added | Usually yes |
expiry | Expired | Usually yes |
rollover | Rolled over | Optional |
void | Removed | Optional |
authorize | Hold placed | Often hide or show as "Reserved" |
release_authorization | Hold released | Often hide |
adjustment | Adjustment | Optional |
For a simple usage feed, filter with type[in]=["capture","capture_authorization","allocation","expiry"] (or filter client-side to those type values) so holds don't clutter the list.
Use metadata for display context
If you attach a metadata object when recording operations, Chargebee Billing stores and returns it verbatim. Use it to enrich each row—for example, the project, workspace, or user a consumption belongs to.
Metadata is for display, not for math
The API does not filter or group by metadata server-side, and results are paginated. Client-side "group by project" or "total per user" over fetched pages will be incomplete. Use metadata to annotate the transactions you display; don't use it to compute totals or filtered aggregates.
Summary
In this tutorial, you learned how to:
- Fetch a real-time credit balance with List Ledger Account Balances and map available, on-hold, and overdraft amounts to your UI.
- List grant blocks for a per-grant breakdown of granted, used, remaining, expiry, and source.
- List ledger operations for a recent activity feed, including optional metadata for display context.
- Proxy all Chargebee Billing calls from your backend and shape responses for your front end.
Reference
We're always happy to help you with any questions you might have! Click here to reach out to us.