Pricing and margins
costToCredits economics - creditsPerUsd, markups per category, the quantization step, the minimum charge, and the plan-solvency check.
Price any debit that has a real upstream cost through costToCredits(costUsd, category?) rather than hand-picking a number. It applies the shared economics on pricingConfig.credits:
| Key | Shipped | Meaning |
|---|---|---|
creditsPerUsd | 1 | Credits per 1 USD of underlying cost. Must be present, finite, and > 0 - costToCredits throws otherwise rather than silently under-billing every metered run. |
markupPercent | 100 | The default margin on every cost-priced debit - AI, external APIs, your features. 100 charges double cost. |
markupByCategory | - | Per-category overrides of markupPercent. Omit it and one margin covers everything. |
step | 0.000001 | The amount every debit ceils UP to, in credits - the ledger's own scale. Max 1; a coarser step over-bills small calls: at 0.0001 a 0.000044-credit model call bills 0.0001. |
minCharge | - | A floor applied AFTER the step, for a fixed per-call overhead the provider's price does not cover. Ceiled to the step itself; max 10 credits. |
Margining services apart
A per-call search vendor and token-billed AI rarely deserve the same margin, so a debit may name the category it belongs to:
credits: {
creditsPerUsd: 1,
markupPercent: 100,
markupByCategory: { ai: 100, webSearch: 50, webExtract: 25 }
}| Category | Covers |
|---|---|
ai | Model usage on a built-in key: chat, chat titles, automations. |
webSearch | The web_search tool's per-call search-vendor charge. |
webExtract | The web_extract tool's per-page extraction charge. |
- Your own feature picks its own key:
costToCredits(cost, "serp"), then add aserpentry when you want it margined apart. - A category with no entry takes
markupPercent, so adding the map re-prices only what it names.0bills that category at cost - legitimate, but it becomes yourlowestMarkup, so re-run the solvency check below before shipping it. - A broken entry throws at debit time (non-finite or negative), exactly like a broken
markupPercent. An unrecognized key is ignored silently, since it may be a category you have not shipped yet - check spelling against the table.
The category is not the ledger's feature tag. Web-tool calls settle under the run's own tag (ai.chat) alongside the model usage they run beside, so pricing needs a dimension of its own.
The solvency check
Credit packs and custom purchases sell 1 credit for 1 base-currency unit, so at the shipped values a credit carries about 0.50 USD of cost - a ~50% gross margin. Whenever you change plan credits, prices, or any markup, verify each plan still holds at your lowest markup:
plan.credits / (creditsPerUsd * (1 + lowestMarkup / 100)) < plan revenue per credit intervallowestMarkupis the smallest ofmarkupPercentand everymarkupByCategoryentry. A plan's whole allotment can go to the one service you margin least, so the cheapest category sets the worst case - at the shipped values an at-cost category doubles a 40-credit plan's worst case from $20 to $40.- Check every interval, not the monthly sticker. A discounted yearly price earns less per 30-day credit grant than the monthly one, so it is the interval that fails first.
pricing-money.test.tsruns this check over every plan, price, and currency you ship. - Non-USD storefronts: providers bill in USD while your prices are in your base currency. If it is weak against the dollar (or zero-decimal, like JPY), re-run the check in USD terms and raise prices or
creditsPerUsd. - Lifetime prices: a lifetime holding of any non-free plan receives its allotment exactly once, at purchase, whatever its
creditInterval- the interval applies only to recurring purchases.