Overview
Many SaaS businesses — AI platforms selling token credits, cloud providers offering compute hours, or API vendors metering calls — need prepaid billing. Customers purchase credits up front, and usage draws down the balance in real time. Aviate Wallets give you a credit pool per account that supports free and paid credits, automatic replenishment (top-off), credit expiration, and a full ledger of every transaction.
This guide walks through creating a wallet, checking balances, adding credits, and configuring automatic top-off — all with standalone curl examples you can run against a local Kill Bill instance.
Prerequisites
-
A running Kill Bill instance with the Aviate plugin installed.
-
The wallet feature flag enabled — start Kill Bill with the following system property:
com.killbill.billing.plugin.aviate.enableWalletApis=trueFor details on setting configuration properties, refer to the Kill Bill Configuration Guide.
-
A valid JWT ID token (see Aviate Authentication).
-
A Kill Bill account with a default payment method (required for paid credits and top-off). See Aviate Billing Accounts.
Key Concepts
Balance vs. Live Balance
The wallet exposes two balance values:
-
balance— the confirmed available credits. This is the amount the customer can spend right now. -
liveBalance— the balance minus any credits reserved for in-progress invoice generation.
Under normal operation these two values are equal. They diverge briefly while Kill Bill is generating an invoice that consumes wallet credits; once the invoice finalizes, the values converge again.
|
Tip
|
If you see a difference between balance and liveBalance, it simply means an invoice is being processed. No action is required.
|
Credit Types
Every credit added to a wallet has one of three types:
-
CREDIT_FREE— credits given at no charge (promotional sign-up bonus, loyalty reward). No invoice or payment is generated. -
CREDIT_PAID— credits purchased by the customer. Kill Bill creates an invoice for the credit amount and attempts payment via the account’s default payment method. -
CREDIT_USED— credits consumed by usage-based invoicing. This type is system-managed; you never createCREDIT_USEDrecords directly.
Top-Off Modes
Automatic top-off replenishes the wallet when the balance drops below a threshold (lowWatermark). Two modes are available:
-
TOP_OFF_FIXED— adds a fixed amount of credits regardless of the current balance. Example: iflowWatermarkis $10 andamountis $50, the system adds exactly $50 when the balance falls below $10. -
TOP_OFF_TARGET— adds enough credits to bring the balance up to a target amount. Example: iflowWatermarkis $10 andamount(the target) is $100, and the current balance is $7, the system adds $93.
Both modes trigger a paid-credit cycle: an invoice is created and payment is attempted automatically.
Step-by-Step: Create a Wallet and Manage Credits
Step 1: Create a Wallet with Free Initial Credits
curl -v -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
-d '{
"kbAccountId": "e5d3d5b5-4415-4166-b57f-33ca00a59e88",
"currency": "USD",
"initCredit": {
"creditType": "CREDIT_FREE",
"amount": "25.00"
}
}' \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet
Expected Response (HTTP 201)
{
"wallet": {
"walletId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"kbAccountId": "e5d3d5b5-4415-4166-b57f-33ca00a59e88",
"currency": "USD",
"balance": 25.00,
"liveBalance": 25.00,
"records": [
{
"creditType": "CREDIT_FREE",
"originAmount": 25.00,
"remainAmount": 25.00,
"description": "Initial free credit"
}
]
},
"status": "WALLET_SUCCESS"
}
|
Note
|
Because the initial credit is CREDIT_FREE, no invoice or payment is generated.
|
Step 2: Check Wallet Balance
curl -H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet/e5d3d5b5-4415-4166-b57f-33ca00a59e88
Expected Response (HTTP 200)
{
"wallet": {
"walletId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"kbAccountId": "e5d3d5b5-4415-4166-b57f-33ca00a59e88",
"currency": "USD",
"balance": 25.00,
"liveBalance": 25.00,
"records": [
{
"creditType": "CREDIT_FREE",
"originAmount": 25.00,
"remainAmount": 25.00,
"description": "Initial free credit"
}
]
},
"status": "WALLET_SUCCESS"
}
Step 3: Add Paid Credits
curl -v -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
-d '{
"creditType": "CREDIT_PAID",
"amount": "100.00"
}' \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet/a1b2c3d4-e5f6-7890-abcd-ef1234567890/credit
Expected Response (HTTP 200)
{
"wallet": {
"walletId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"kbAccountId": "e5d3d5b5-4415-4166-b57f-33ca00a59e88",
"currency": "USD",
"balance": 125.00,
"liveBalance": 125.00,
"records": [
{
"creditType": "CREDIT_FREE",
"originAmount": 25.00,
"remainAmount": 25.00,
"description": "Initial free credit"
},
{
"creditType": "CREDIT_PAID",
"originAmount": 100.00,
"remainAmount": 100.00,
"kbInvoiceId": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
"kbPaymentId": "12345678-abcd-ef01-2345-6789abcdef01"
}
]
},
"status": "WALLET_SUCCESS"
}
|
Important
|
Adding paid credits triggers an invoice for $100 and attempts payment via the account’s default payment method. If no default payment method is set, the status will be WALLET_PAYMENT_FAILED and the credits remain inactive until payment succeeds.
|
Step 4: Configure Automatic Top-Off
Option A: Fixed-Amount Top-Off
curl -v -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
-d '{
"topOffType": "TOP_OFF_FIXED",
"lowWatermark": "10.00",
"amount": "50.00",
"expDurationUnit": "MONTHS",
"expDurationLength": 6
}' \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet/a1b2c3d4-e5f6-7890-abcd-ef1234567890/topOffConfig
How this works: When the wallet balance drops below $10 (the lowWatermark), the system automatically adds $50 of paid credits. An invoice is created and payment attempted. The new credits expire 6 months after the top-off event.
Option B: Target-Balance Top-Off
curl -v -X PUT \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
-d '{
"topOffType": "TOP_OFF_TARGET",
"lowWatermark": "10.00",
"amount": "100.00",
"expDurationUnit": "MONTHS",
"expDurationLength": 12
}' \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet/a1b2c3d4-e5f6-7890-abcd-ef1234567890/topOffConfig
How this works: When the wallet balance drops below $10, the system adds enough credits to bring the balance back up to $100. If the balance is $7, it adds $93. Credits expire 12 months after each top-off.
|
Tip
|
Use TOP_OFF_TARGET when you want a predictable credit pool size. Use TOP_OFF_FIXED when you want consistent, smaller recharges.
|
Step 5: Observe Credit Consumption
After usage-based invoicing consumes wallet credits, check the wallet to see the reduced balance:
curl -H "Authorization: Bearer ${ID_TOKEN}" \
-H "X-Killbill-ApiKey: my-tenant" \
-H "X-Killbill-ApiSecret: my-secret" \
http://127.0.0.1:8080/plugins/aviate-plugin/v1/wallet/e5d3d5b5-4415-4166-b57f-33ca00a59e88
Expected Response (balance reduced after usage)
{
"wallet": {
"walletId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"kbAccountId": "e5d3d5b5-4415-4166-b57f-33ca00a59e88",
"currency": "USD",
"balance": 105.00,
"liveBalance": 105.00,
"records": [
{
"creditType": "CREDIT_FREE",
"originAmount": 25.00,
"remainAmount": 5.00
},
{
"creditType": "CREDIT_PAID",
"originAmount": 100.00,
"remainAmount": 100.00,
"kbInvoiceId": "f9e8d7c6-b5a4-3210-fedc-ba9876543210",
"kbPaymentId": "12345678-abcd-ef01-2345-6789abcdef01"
},
{
"creditType": "CREDIT_USED",
"originAmount": -20.00,
"remainAmount": 0.00,
"kbInvoiceId": "aabbccdd-1122-3344-5566-778899aabbcc"
}
]
},
"status": "WALLET_SUCCESS"
}
In this example, $20 of usage was invoiced: the free-credit record decreased from 25.00 to 5.00, and a CREDIT_USED record was added.
Credit Expiration
Credits can include an optional expiration date (expDate). When creating a wallet with initial credits or adding credits later, you can set an expiration:
{
"creditType": "CREDIT_PAID",
"amount": "50.00",
"expDate": "2026-12-31T23:59:59Z"
}
When credits expire:
-
The expired credit’s
remainAmountis set to zero. -
The wallet
balancedecreases by the remaining unused amount. -
Expired credits still appear in the wallet
recordsfor auditing purposes.
For automatic top-off, use expDurationUnit (DAYS, MONTHS, YEARS) and expDurationLength to set a rolling expiration relative to each top-off event.
Failed Payments and Credit Activation
When paid credits are added but payment fails, the wallet response returns a WALLET_PAYMENT_FAILED status. The credit record is created but remains inactive — it does not contribute to the balance until payment succeeds.
To resolve this:
-
Ensure the account has a valid default payment method (see Aviate Billing Accounts).
-
Retry the payment against the pending invoice using the Kill Bill Trigger Payment for Invoice API.
-
Once payment succeeds, the credits become active and the wallet balance updates automatically.
|
Note
|
The same behavior applies to automatic top-off: if top-off triggers but payment fails, the credits remain pending until the invoice is paid. |
Wallet Records (Ledger)
The wallet maintains a complete ledger in the records array. Each record represents a credit event and includes:
| Field | Description |
|---|---|
|
|
|
The original credit amount (positive for additions, negative for consumption). |
|
The remaining unused amount. |
|
Expiration date (if set), after which the credit is no longer available. |
|
The Kill Bill invoice ID (present for |
|
The Kill Bill payment ID (present for |
|
Optional description for the credit entry. |
WalletOpStatus Reference
Every wallet API response includes a status field indicating the outcome:
| Status | Meaning |
|---|---|
|
Operation completed successfully. |
|
General failure (e.g., invalid request parameters). |
|
Kill Bill failed to create the invoice for paid credits. |
|
Invoice was created but payment failed. Credits remain inactive. |
|
Payment is in progress but has not yet completed. |
What to Verify
After completing the steps above, confirm the following:
-
The wallet balance reflects added credits (free + paid).
-
Paid credits generate a Kill Bill invoice — verify via the Kill Bill
/1.0/kb/invoicesendpoint. -
Top-off triggers automatically when balance drops below
lowWatermark. -
Expired credits are no longer included in the wallet
balance. -
CREDIT_USEDrecords appear in the ledger after usage-based invoicing.
Common Pitfalls
-
No default payment method — paid credits and top-off both require a default payment method on the account. Without one, the status will be
WALLET_PAYMENT_FAILEDand credits remain inactive. -
lowWatermarkset too close to zero — usage spikes may drain the balance before top-off triggers. Set the watermark high enough to cover expected usage between billing cycles. -
One wallet per account — each account supports a single wallet in a single currency (the account’s default currency). You cannot create multi-currency wallets.
-
CREDIT_FREEdoes not trigger invoices — free credits are granted immediately with no payment flow. Do not expect an invoice for promotional credits. -
balancevs.liveBalancediscrepancy — this is normal during invoice generation. The values converge once the invoice finalizes. No manual intervention is needed.
API Reference
| Method | Path | Description |
|---|---|---|
|
|
Create a wallet with optional initial credits and top-off configuration. |
|
|
Retrieve wallets for an account. |
|
|
Add credits (free or paid) to an existing wallet. |
|
|
Configure or update automatic top-off rules. |
Related
-
Aviate Authentication — obtain the
ID_TOKENused in all examples above -
Aviate Metering — usage events that consume wallet credits
-
Usage + Wallet Tutorial — complete end-to-end tutorial combining metering, wallets, and invoicing
-
Aviate Coupons — discounts vs. wallet credits