Prepaid Credits
AI products incur costs immediately — with every token generated, every video rendered, every API call processed. Unlike traditional SaaS where you can bill at the end of a period and absorb the cost in the meantime, AI infrastructure costs are real-time and often unpredictable. A single customer running a large batch job can cost you significantly before you've collected a cent.
Prepaid credits address this. Customers fund a wallet before they consume, and usage draws down that balance in real time. You never front costs, cash flow is protected, and customers always have visibility into what they're spending.
Beyond the financial mechanics, prepaid credits also let you abstract away the complexity of variable infrastructure costs into product-native units — tokens, video minutes, generation credits — that make intuitive sense to your customers rather than exposing raw dollar fractions per API call.
This article covers wallets, credit grants, custom assets, and how to accept and manage customer payments. For the product configuration and usage event side of things, see Usage Based Billing.
Wallets and Accounts
Every customer in Credyt has one wallet. That wallet can hold multiple accounts, one per asset. Accounts are created automatically when a customer is subscribed to a product — no separate setup required.
A wallet can hold:
- Fiat accounts — balances in standard currencies like USD or EUR, debited directly in currency terms (e.g. $1.00 per video minute)
- Custom asset accounts — balances in units you define, like
VIDGENMIN(video generation minutes) orCREDIT, debited in those units as usage occurs
Both account types can coexist in the same wallet. A customer can hold a USD balance and a token balance simultaneously, with usage events drawing from the correct account based on what asset the product price is denominated in.
Example wallet response:
- REST API
- TypeScript SDK
Response
{
"accounts": [
{
"id": "default:USD",
"name": "default",
"asset": "USD",
"available": 14.57
},
{
"id": "default:VIDGENMIN",
"name": "default",
"asset": "VIDGENMIN",
"available": 320
}
]
}
await client.customerWalletOps.getCustomerWallet("cust_473cr1y0ghbyc3m1yfbwvn3nxx");
SDK response object
const response = {
accounts: [
{
id: "default:USD",
name: "default",
asset: "USD",
available: 14.57,
},
{
id: "default:VIDGENMIN",
name: "default",
asset: "VIDGENMIN",
available: 320,
},
],
};
Here the customer has a USD balance for fiat-priced products and a Video Generation Minutes balance for usage priced in that custom asset.
Fiat vs Custom Assets
Each product price is tied to a specific asset — USD, tokens, minutes — and that's the account Credyt debits when a usage event arrives. Choosing between fiat and custom assets is one of the most consequential decisions in your billing setup.
Price in fiat (USD, EUR) when:
- Your costs are predictable per unit and straightforward to express in dollar terms
- Your customers are developers or technical users who expect to see dollar costs
- You want simple reconciliation with no conversion layer
Price in custom assets when:
- Infrastructure costs vary by workload and you want to abstract that variance behind stable unit pricing
- You want product-native units that map to what customers actually do (minutes generated, not dollars spent)
- You expect your pricing to evolve as you learn your unit economics, and want to adjust rates without changing the customer-facing unit
- You want customers to be able to earn credits through referrals, daily bonuses, or promotions — not just purchase them
Both approaches are fully supported and can coexist within the same wallet.
Creating a custom asset
Assets must be created before any product that references them. Define the asset code, name, display label, precision, and exchange rate against fiat.
In this example we create a VIDGENMIN asset — Video Generation Minutes — priced at $0.10 per minute (or 10 minutes per $1.00):
- REST API
- TypeScript SDK
{
"code": "VIDGENMIN",
"name": "Video Generation Minutes",
"precision": 2,
"symbol": "⏰",
"label": "MIN",
"rates": [
{
"source": "USD",
"rate": 0.1
}
]
}
Response
{
"code": "VIDGENMIN",
"created_at": "2026-02-06T10:01:29.686322Z"
}
await client.assets.create({
code: "VIDGENMIN",
name: "Video Generation Minutes",
precision: 2,
symbol: "⏰",
label: "MIN",
rates: [
{
source: "USD",
rate: 0.1,
},
],
});
SDK response object
const response = {
code: "VIDGENMIN",
createdAt: "2026-02-06T10:01:29.686322Z",
};
Once the asset exists, you can reference it in your product pricing configuration and Credyt will use the exchange rate to convert fiat payments into asset units when customers top up.
Exchange rates can be updated at any time and changes can be scheduled to take effect at a future date, so you can adjust pricing without surprising customers mid-session. See Assets for full asset management details.
Credit Grants
A wallet balance isn't a single undifferentiated pool of funds. Under the hood, Credyt models it as a sequence of credit grants — discrete allocations of prepaid value, each with its own lifecycle, purpose, and financial attributes.
This matters for several practical reasons:
- Expiry and breakage — grants have explicit expiry dates. When a grant expires, unused value is recognized as breakage revenue and the grant is excluded from available balance.
- Exchange rate locking — for custom assets, the exchange rate at the time of purchase is locked to each grant. This ensures accurate fiat revenue reporting even if rates change between top-up and consumption.
- Auditability — every grant has a clear origin, purpose, and lifetime, all visible and queryable via the API.
- Revenue recognition — revenue is recognized when credits are consumed or expire, not when they are purchased. At the point of top-up, the value is recorded as deferred revenue and moves to recognized revenue as usage draws it down.
How grants are issued
Credit grants enter a wallet through a few well-defined paths:
- Top-up — when a customer completes a payment, Credyt automatically creates a
paidgrant. Paid grants expire after one year by default. - Wallet adjustment — your platform can issue grants directly, for example as promotional credits or manual top-ups. The
reasonandexpires_atparameters control how the grant is classified. - Entitlements — when a customer is subscribed to a hybrid product with bundled credits, Credyt automatically issues grants according to the entitlement configuration. See Hybrid Billing with Entitlements.
Consumption order
Credyt consumes grants in the order they expire — the earliest-expiring grants are drawn down first. This ensures customers use credits before they expire.
Example: querying active grants for a customer
- REST API
- TypeScript SDK
await client.customerWalletOps.getCreditGrants("cust_473cr1y0ghbyc3m1yfbwvn3nxx", "default:USD");
The promotional grant expires sooner, so Credyt will consume it first unless priority rules dictate otherwise.
For more details on credit grants, see our Credit Grants article.
Funding the Wallet
Credyt gives you three ways to accept customer payments and fund wallet balances. Choose based on how much control you want over the payment flow.
When a payment is designated for a custom asset account, Credyt automatically converts the fiat amount to the corresponding asset units using the predefined exchange rate.
Option 1: Billing Portal
The simplest option. Credyt hosts a self-service billing centre where customers can manage their own balance — view transactions, add funds, configure auto top-up, and see usage history. You generate a session link and redirect the customer; Credyt handles everything else.
- No payment UI to build
- No sign-up required for the customer
- Supports both fiat and custom asset top-ups with automatic conversion
For more details on UX, see Billing Portal. Setup and session management details are available in Quickstart.
Option 2: Top-up API
Use this when you want to embed a top-up flow directly in your product UI but still use Credyt's payment processing. You define the amount, Credyt creates a Stripe checkout session and returns a redirect URL.
- You control when and how customers are prompted to top up
- Credyt handles the Stripe integration and payment confirmation
- Supports exchange rate overrides for custom assets, useful for volume incentives or tiered pricing
See Wallets and Top-ups for further details.
Option 3: Wallet Adjustments
Use this when you process payments through your own payment provider, or when you need to add balance manually — for example, to issue promotional credits or to pre-fund a wallet during development and testing.
- Your PSP handles payment collection
- You call the Adjustments API after a successful payment to credit the wallet
- The
reasonfield controls how the resulting credit grant is classified (external_topup,promotional, etc.)
For more on adjustments, including charges and gifting credits, see Adjustments.
Checking the Balance
You can query a customer's wallet balance at any time. A common pattern is to check the balance before allowing a billable action, and gate access if the balance is insufficient.
You can get the full wallet or a specific account:
- REST API
- TypeScript SDK
Response
{
"id": "default:VIDGENMIN",
"name": "default",
"asset": "VIDGENMIN",
"available": 320,
"pending_in": 100,
"pending_out": 0
}
await client.customerWalletOps.getAccount("cust_473cr1y0ghbyc3m1yfbwvn3nxx", "default:VIDGENMIN");
SDK response object
const response = {
id: "default:VIDGENMIN",
name: "default",
asset: "VIDGENMIN",
available: 320,
pendingIn: 100,
pendingOut: 0,
};
available reflects the balance that can be spent right now. pending_in represents top-ups that have been initiated but not yet confirmed.
Auto Top-up
Auto top-up lets customers keep their wallet funded automatically without manual intervention. This is a self-serve functionality where customers configure a threshold and a recharge amount per asset in the Billing Portal. When the balance for a given asset drops below the threshold, Credyt initiates an off-session Stripe payment and credits the wallet automatically.
This is particularly important for:
- Long-running or background workloads where a depleted balance mid-task would cause a failure
- Products where usage is unpredictable and customers can't easily anticipate when to top up
- Consumer products where you want a seamless experience and minimal billing friction
Auto top-up is configured per asset, so a customer can set independent rules for their USD account and their VIDGENMIN account. All card management is handled through the Billing Portal. See Auto Top-up for details.
Setup Flow
Step 1: Create a custom asset
Define the asset code, name, and exchange rate against fiat. Skip this step if you are billing directly in USD or EUR.
- REST API
- TypeScript SDK
{
"code": "VIDGENMIN",
"name": "Video Generation Minutes",
"precision": 2,
"symbol": "⏰",
"label": "MIN",
"rates": [
{
"source": "USD",
"rate": 0.1
}
]
}
Response
{
"code": "VIDGENMIN",
"created_at": "2026-02-06T10:01:29.686322Z"
}
await client.assets.create({
code: "VIDGENMIN",
name: "Video Generation Minutes",
precision: 2,
symbol: "⏰",
label: "MIN",
rates: [
{
source: "USD",
rate: 0.1,
},
],
});
SDK response object
const response = {
code: "VIDGENMIN",
createdAt: "2026-02-06T10:01:29.686322Z",
};
Step 2: Create a product
Define a usage-based product that references your asset in its pricing configuration. See Usage Based Billing for the full product setup guide.
- REST API
- TypeScript SDK
{
"name": "Glitch Video",
"code": "glitch_video_std",
"prices": [
{
"name": "Video Generation",
"type": "usage_based",
"billing_model": {
"type": "real_time"
},
"usage_calculation": {
"event_type": "video_generated",
"usage_type": "volume",
"volume_field": "minutes"
},
"pricing": [
{
"asset": "VIDGENMIN",
"values": [
{
"volume_rate": 1
}
]
}
]
}
],
"publish": true
}
Response
{
"id": "prp_4e28n8kk41931f5yt5em49ecw7",
"code": "glitch_video_std",
"version": 1,
"status": "published",
"is_default": true
}
await client.products.create({
name: "Glitch Video",
code: "glitch_video_std",
prices: [
{
name: "Video Generation",
type: "usage_based",
billingModel: {
type: "real_time",
},
usageCalculation: {
eventType: "video_generated",
usageType: "volume",
volumeField: "minutes",
},
pricing: [
{
asset: "VIDGENMIN",
values: [
{
volumeRate: 1,
},
],
},
],
},
],
publish: true,
});
SDK response object
const response = {
id: "prp_4e28n8kk41931f5yt5em49ecw7",
code: "glitch_video_std",
version: 1,
status: "published",
isDefault: true,
};
Step 3: Subscribe your customer
When the subscription is created, Credyt automatically provisions the wallet accounts required by the product's pricing — no additional setup needed.
- REST API
- TypeScript SDK
{
"name": "Walter Kreiger",
"external_id": "18991",
"subscriptions": [
{
"products": [
{
"code": "glitch_video_std",
"version": "default"
}
]
}
]
}
Response
{
"id": "cust_473cr1y0ghbyc3m1yfbwvn3nxx",
"subscriptions": [
{
"id": "sub_411xhg4kqakf3d8ybezbzta558",
"started_at": "2025-08-24T14:15:22Z",
"status": "active",
"products": [
{
"id": "prp_4e28n8kk41931f5yt5em49ecw7",
"code": "glitch_video_std",
"version": "default"
}
]
}
]
}
await client.customers.create({
name: "Walter Kreiger",
externalId: "18991",
subscriptions: [
{
products: [
{
code: "glitch_video_std",
version: "default",
},
],
},
],
});
SDK response object
const response = {
id: "cust_473cr1y0ghbyc3m1yfbwvn3nxx",
subscriptions: [
{
id: "sub_411xhg4kqakf3d8ybezbzta558",
startedAt: "2025-08-24T14:15:22Z",
status: "active",
products: [
{
id: "prp_4e28n8kk41931f5yt5em49ecw7",
code: "glitch_video_std",
version: "default",
},
],
},
],
};
Step 4: Fund the wallet
Redirect the customer to the Billing Portal, initiate a top-up via the API, or credit the wallet directly via a wallet adjustment. See Funding the Wallet above.
Step 5: Check the balance before billing
Query the wallet before allowing a billable action to confirm the customer has sufficient balance.
- REST API
- TypeScript SDK
Response
{
"id": "default:VIDGENMIN",
"name": "default",
"asset": "VIDGENMIN",
"available": 320,
"pending_in": 100,
"pending_out": 0
}
await client.customerWalletOps.getAccount("cust_473cr1y0ghbyc3m1yfbwvn3nxx", "default:VIDGENMIN");
SDK response object
const response = {
id: "default:VIDGENMIN",
name: "default",
asset: "VIDGENMIN",
available: 320,
pendingIn: 100,
pendingOut: 0,
};
See Also
- Usage-Based Billing — defining products, usage calculation modes, and sending usage events
- Credit Grants — full grant lifecycle, consumption rules, and API reference
- Assets — custom asset management, exchange rates, and quoting
- Hybrid Billing with Entitlements — bundled credit allowances combined with subscriptions