Billing credits
A billing credit is a prepaid balance attached to a customer or account. When a billing meter records usage, Frame can decrement the customer's matching credit balance rather than (or in addition to) generating an invoice line. This is the primitive behind prepaid pricing models — buy 10,000 API calls upfront, use them down, repurchase when depleted.
Owner: customer or account
A credit attaches to exactly one of:
customer_id— B2C balance. The customer's individual prepaid pool.account_id— B2B balance. The account's pool, typically shared across the account's users.
Mutually exclusive. Frame's validator rejects a credit with both set.
Fields
curl --request POST \
--url https://api.framepayments.com/v1/billing/billing_credit \
--header "Authorization: Bearer $FRAME_SECRET_KEY" \
--header 'Content-Type: application/json' \
--data '{
"customer": "<customer_id>",
"product": "<product_id>",
"billing_credits": 10000,
"limited": true,
"expires": "2027-06-05T00:00:00Z"
}'
The interesting fields:
customeroraccount— owner. Mutually exclusive.product— FK to a Product. Credits are product-scoped: usage against a different product can't consume them. The Product'sBillingProductDetailcarries thebilling_type(prepaid/postpaid/ etc.) and credit-related configuration.billing_credits— the prepaid balance, in whatever unit the meter measures (calls, gigabytes, minutes — whatever the meter aggregates).limited— boolean. When true, usage is capped at the credit balance; when false, credits decrement but additional usage continues at on-demand pricing. Defaults vary byBillingProductDetail.expires— ISO-8601 timestamp. Credits inactivestate past this time transition toexpired.
On create, Frame populates derived fields:
available_credits— starts equal tobilling_credits; decrements as usage events arrive.used_credits— starts at 0; increments as usage flows through meters.
The math invariant: billing_credits = available_credits + used_credits.
Lifecycle
Credits walk through three states:
inactive(initial) — credit exists but isn't yet consumable. Useful for staging credits ahead of activation, or for credits awaiting payment confirmation.active— consumable. Usage events that match the product decrementavailable_credits.expired— past theexpirestimestamp, or manually expired. Terminal.
Transitions:
inactive → activeviaactivate.active → inactiveviadeactivate.active → expiredviaexpire(automatic whenexpirespasses, or manual).
expired is terminal — credits don't come back to active once expired. Issue a new credit if you need to extend the balance.
How credits consume
When a BillingMetricEvent lands for a customer who has active credits scoped to the meter's product:
- Frame computes the cost (per the meter's aggregation +
value). - If credits are available, deducts from
available_credits, incrementsused_credits. - If credits aren't enough and
limited: false, the remainder bills at on-demand rates (invoice line on the next cycle). - If credits aren't enough and
limited: true, the usage is rejected at the application layer (your platform enforces; Frame surfaces the threshold-progress signal).
The threshold-progress endpoint surfaces depletion state:
curl --request GET \
--url "https://api.framepayments.com/v1/billing/report/threshold_progress?customer_id=<customer_id>" \
--header "Authorization: Bearer $FRAME_SECRET_KEY"
Returns the remaining credits per active credit record. Use this for customer-facing usage dashboards ("you have 2,317 calls remaining this cycle").
Webhooks
Credit-related events emit through the broader billing webhook set:
billing.credits_usage— credits were consumed against a usage event.billing.threshold.approaching— customer is close to running out. Threshold typically configured server-side; reach out to Frame ops if you need a custom threshold.billing.credits_expiration— credits expiring soon or just expired.
Subscribe to billing.threshold.approaching to give customers a heads-up before they hit zero — this is the most leverage-positive signal in the billing webhook surface for customer retention.
Composition with subscriptions
Credits + subscriptions compose in two common shapes:
- Subscription bundles credits. A monthly subscription product includes N credits per cycle. On renewal, issue a fresh credit; on cancellation, expire the remaining balance (or let it expire naturally).
- Credits replace subscription. Customer purchases credits a la carte; no recurring subscription. Customer's checkout flow is "buy more credits" rather than "manage subscription."
The Product's BillingProductDetail.billing_type and billing_credits fields control this composition.
Gotchas
Symptom: you issued a credit but available_credits stays at zero. Why: the credit was created in inactive (the initial state) and never activated. Fix: explicitly call activate, or check whether your creation flow auto-activates depending on BillingProductDetail.billing_type.
Symptom: customer's usage isn't decrementing their credit. Why: the credit is product-scoped; usage that doesn't match the credit's product (i.e., events on a meter whose attached Product is different) won't consume. Fix: either issue a credit on the right product, or restructure your meters/products so the usage matches.
Symptom: duplicate active credits for the same customer and product. Why: IssueBillingCreditService rejects this on create, but if you bypassed the service (direct DB insert, ops console) you could end up with two. Fix: always use the API; for cleanup, expire the duplicates manually.
Symptom: expired credits are still showing up on the customer's dashboard. Why: getBillingThresholdProgress returns active credits only, but if you're listing credits independently, you'll see expired ones too. Fix: filter by status: active in your dashboard query.
Reference
For the full API surface, see POST/v1/billing/billing_credit and GET/v1/billing/report/threshold_progress.