See your balance, add credit, choose a plan and find out what is costing money. Plany is prepaid: your plan grants credit every month, running resources draw it down by the hour, and when the balance reaches zero your workloads stop and your data is kept for 90 days before it is deleted. Read the ledger, break spending down by SKU, project or label with QueryCosts, and price a workload before you create it with EstimateCost.
namestringRequiredgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "accounts//billing" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/GetBillingAccount{ "name": "accounts//billing", "plan": "plans/my-plan", "balance": { "currency_code": "USD", "units": "5", "nanos": 0 }, "pending_charges": { "currency_code": "USD", "units": "5", "nanos": 0 }, "current_period": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }, "exhaustion_policy": "EXHAUSTION_POLICY_STOP_WORKLOADS", "auto_topup_amount": { "currency_code": "USD", "units": "5", "nanos": 0 }, "auto_topup_threshold": { "currency_code": "USD", "units": "5", "nanos": 0 }, "spend_cap": { "currency_code": "USD", "units": "5", "nanos": 0 }, "alert_thresholds": [ { "currency_code": "USD", "units": "5", "nanos": 0 } ], "payment_method_valid": false, "currency_code": "", "burn_rate_per_day": { "currency_code": "USD", "units": "5", "nanos": 0 }, "projected_exhaustion_time": "2026-10-04T12:00:00Z", "purse_balances": [ { "purse": "PURSE_GENERAL", "balance": { "currency_code": "USD", "units": "5", "nanos": 0 }, "period_spend": { "currency_code": "USD", "units": "5", "nanos": 0 }, "project": "projects/" } ], "subscription": { "plan": "plans/my-plan", "state": "STATE_ACTIVE", "current_period_end": "2026-10-04T12:00:00Z", "cancel_at_period_end": false, "billing_interval": "BILLING_INTERVAL_MONTHLY", "trial_end_time": "2026-10-04T12:00:00Z" }, "trial_available": false}show_unavailableboolInclude plans no longer offered to new accounts.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ api.plany.cloud:443 plany.billing.v1.BillingService/ListPlans{ "plans": [ { "name": "plans/my-plan", "display_name": "My server", "description": "", "monthly_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "included_credit": { "currency_code": "USD", "units": "5", "nanos": 0 }, "credit_rolls_over": false, "max_rollover": { "currency_code": "USD", "units": "5", "nanos": 0 }, "usage_discount_basis_points": 0, "plan_id": "my-plan", "available": false, "support_tier": "", "annual_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "annual_included_credit": { "currency_code": "USD", "units": "5", "nanos": 0 }, "trial_days": 0 } ]}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "plans/my-plan" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/GetPlan{ "name": "plans/my-plan", "display_name": "My server", "description": "", "monthly_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "included_credit": { "currency_code": "USD", "units": "5", "nanos": 0 }, "credit_rolls_over": false, "max_rollover": { "currency_code": "USD", "units": "5", "nanos": 0 }, "usage_discount_basis_points": 0, "plan_id": "my-plan", "available": false, "support_tier": "", "annual_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "annual_included_credit": { "currency_code": "USD", "units": "5", "nanos": 0 }, "trial_days": 0}Starts a payment in Stripe Checkout: credit, or a plan subscription.
Returns the session to show the customer. Nothing is granted by this call: credit is added when Stripe confirms the payment, exactly once however many times the confirmation arrives. A plan's monthly credit is added the same way, once per paid invoice. Use GetCheckoutSession to find out whether it has arrived.
Pro can be paid annually by setting billing_interval to BILLING_INTERVAL_ANNUAL with plan: "plans/pro"; Hobby is monthly only. An account's first subscription to Hobby starts with a 7-day free trial: the card is taken now and first charged when the trial ends.
accountstringRequiredrequest_idstringRequiredIdempotency key. A repeated request returns the same session rather than opening a second one.
amountMoneyThe credit to buy, before tax. Whole cents, in the account's currency. Tax is added by Checkout on top and is never credited.
Exactly one of amount and plan.
projectstringBuy credit for one top-up project rather than for the account.
projects/{project}, which must belong to account and be top-up only.
return_pathstringWhere the console returns to once payment completes: a path on the
console, beginning with a single /. Defaults to /billing. A path
rather than a URL so a session can only ever return to Plany.
planstringSubscribe to a plan rather than buy credit: plans/hobby or plans/pro.
Refused for an account that already has a subscription — a switch or a
cancellation is the Customer Portal's.
billing_intervalBillingIntervalHow often the plan is paid for. Unspecified is monthly. Annual is offered
only for a plan with an annual_price. Only with plan.
BILLING_INTERVAL_MONTHLYBILLING_INTERVAL_ANNUALgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "account": "accounts/", "request_id": "0f8fad5b-d9cb-469f-a165-70867728950e" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/CreateCheckoutSession{ "name": "", "client_secret": "", "amount": { "currency_code": "USD", "units": "5", "nanos": 0 }, "project": "projects/", "state": "STATE_OPEN", "ledger_entry": "", "expire_time": "2026-10-04T12:00:00Z", "plan": "", "trial_days": 0, "billing_interval": "BILLING_INTERVAL_MONTHLY"}Opens Stripe's Customer Portal for the account: switching between plans, cancelling, updating the card and reading past invoices. Returns a short-lived URL to send the browser to.
accountstringRequiredreturn_pathstringWhere the portal's "return" link goes: a path on the console beginning
with a single /. Defaults to /billing?tab=plans.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "account": "accounts/" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/CreateBillingPortalSession{ "url": ""}Reads a checkout session back, so a page the customer returns to can say whether the credit has landed yet.
namestringRequiredaccounts/{account}/checkoutSessions/{session}.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/GetCheckoutSession{ "name": "", "client_secret": "", "amount": { "currency_code": "USD", "units": "5", "nanos": 0 }, "project": "projects/", "state": "STATE_OPEN", "ledger_entry": "", "expire_time": "2026-10-04T12:00:00Z", "plan": "", "trial_days": 0, "billing_interval": "BILLING_INTERVAL_MONTHLY"}parentstringRequiredpage_sizeint32page_tokenstringfilterstringAIP-160 filter, e.g. type = "USAGE_CHARGE" AND project = "projects/p1".
intervalIntervalRestrict to entries created in this window.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "parent": "accounts/", "page_size": 50 }' \ api.plany.cloud:443 plany.billing.v1.BillingService/ListLedgerEntries{ "ledger_entries": [ { "name": "accounts//ledgerEntries/my-ledger-entry", "account": "accounts/", "type": "TYPE_PLAN_GRANT", "amount": { "currency_code": "USD", "units": "5", "nanos": 0 }, "balance_after": { "currency_code": "USD", "units": "5", "nanos": 0 }, "description": "", "sku": "", "project": "projects/", "resource": "", "usage_interval": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }, "quantity_micros": "0", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "reverses_entry": "", "actor": "", "idempotency_key": "", "explanation": "", "purse": "PURSE_GENERAL" } ], "next_page_token": ""}Aggregated spend, grouped by SKU, project, service or label. Backs the cost-breakdown views.
accountstringRequiredDimensions to group by, applied in order to produce nested buckets.
GROUP_BY_SKUGROUP_BY_PROJECTGROUP_BY_SERVICEGROUP_BY_LOCATIONGROUP_BY_RESOURCEGROUP_BY_LABELGROUP_BY_DAYGROUP_BY_HOURlabel_keystringLabel key to group by, when group_by contains GROUP_BY_LABEL.
filterstringgrpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "account": "accounts/", "interval": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" } }' \ api.plany.cloud:443 plany.billing.v1.BillingService/QueryCosts{ "buckets": [ { "keys": [ "" ], "cost": { "currency_code": "USD", "units": "5", "nanos": 0 }, "quantity_micros": "0", "usage_unit": "" } ], "total": { "currency_code": "USD", "units": "5", "nanos": 0 }}Prices a hypothetical workload without creating it — what a server of a given size would cost per hour, for example, before you choose it.
skustringRequiredquantity_microsint64RequiredQuantity in millionths of the SKU's usage unit.
locationstringaccountstringPrice as this account would be charged, applying its plan discount and remaining free allowances. Empty gives undiscounted list prices.
planstringPrice as this plan would be charged, for comparing plans before committing to one.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "line_items": [] }' \ api.plany.cloud:443 plany.billing.v1.BillingService/EstimateCost{ "line_items": [ { "sku": "", "display_name": "My server", "quantity_micros": "0", "usage_unit": "", "cost": { "currency_code": "USD", "units": "5", "nanos": 0 }, "free_quantity_micros": "0" } ], "total": { "currency_code": "USD", "units": "5", "nanos": 0 }, "plan_discount": { "currency_code": "USD", "units": "5", "nanos": 0 }}page_sizeint32page_tokenstringfilterstringAIP-160 filter, e.g. service = "microvm.plany.cloud".
locationstringReturn prices for this location rather than the defaults.
grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "page_size": 50 }' \ api.plany.cloud:443 plany.billing.v1.BillingService/ListSkus{ "skus": [ { "name": "skus/my-sku", "display_name": "My server", "description": "", "service": "", "category": "", "price": { "pricing_type": "PRICING_TYPE_PER_HOUR", "usage_unit": "", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "tiering_mode": "TIERING_MODE_FLAT", "free_quantity_per_month": "0", "minimum_billable_quantity": "0" }, "location_prices": { "key": { "pricing_type": "PRICING_TYPE_PER_HOUR", "usage_unit": "", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "tiering_mode": "TIERING_MODE_FLAT", "free_quantity_per_month": "0", "minimum_billable_quantity": "0" } }, "quota_metric": "", "create_time": "2026-10-04T12:00:00Z", "effective_end_time": "2026-10-04T12:00:00Z", "public": false } ], "next_page_token": ""}grpcurl -H "authorization: Bearer $PLANY_API_KEY" \ -d '{ "name": "skus/my-sku" }' \ api.plany.cloud:443 plany.billing.v1.BillingService/GetSku{ "name": "skus/my-sku", "display_name": "My server", "description": "", "service": "", "category": "", "price": { "pricing_type": "PRICING_TYPE_PER_HOUR", "usage_unit": "", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "tiering_mode": "TIERING_MODE_FLAT", "tiers": [ { "start_quantity": "0", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 } } ], "free_quantity_per_month": "0", "minimum_billable_quantity": "0" }, "location_prices": { "key": { "pricing_type": "PRICING_TYPE_PER_HOUR", "usage_unit": "", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "tiering_mode": "TIERING_MODE_FLAT", "tiers": [ { "start_quantity": "0", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 } } ], "free_quantity_per_month": "0", "minimum_billable_quantity": "0" } }, "quota_metric": "", "create_time": "2026-10-04T12:00:00Z", "effective_end_time": "2026-10-04T12:00:00Z", "public": false}One thing to price.
skustringRequiredquantity_microsint64RequiredQuantity in millionths of the SKU's usage unit.
locationstring{ "sku": "", "quantity_micros": "0", "location": ""}Priced line, mirroring the request order.
skustringdisplay_namestringquantity_microsint64usage_unitstringcostMoneyfree_quantity_microsint64Portion covered by a free allowance rather than charged.
{ "sku": "", "display_name": "My server", "quantity_micros": "0", "usage_unit": "", "cost": { "currency_code": "USD", "units": "5", "nanos": 0 }, "free_quantity_micros": "0"}LedgerEntry is one immutable movement of credit. The balance is the sum of entries; nothing overwrites a balance in place, so any figure can be explained by replaying the entries that produced it.
namestringIdentifieraccountstringOutput onlyTYPE_PLAN_GRANTTYPE_PURCHASETYPE_PROMOTIONAL_GRANTTYPE_USAGE_CHARGETYPE_REFUNDTYPE_ADJUSTMENTTYPE_EXPIRYTYPE_REVERSALTYPE_PLAYER_TOP_UPSigned: positive adds credit, negative spends it.
Balance immediately after this entry, so a statement line needs no running total computed client-side.
descriptionstringOutput onlyskustringOutput onlyFor a USAGE_CHARGE, what it paid for.
projectstringOutput onlyresourcestringOutput onlyquantity_microsint64Output onlyWhen a grant's unspent remainder expires. Grants are consumed expiry-soonest first, so plan credit is spent before purchased credit.
reverses_entrystringOutput onlyEntry this reverses, for REVERSAL and REFUND.
actorstringOutput onlyPrincipal responsible, for ADJUSTMENT and PURCHASE.
idempotency_keystringOutput onlyThe key that produced this entry. Unique per account, so the same usage is never charged twice.
explanationstringOutput onlyHow the charge was arrived at, in words.
The rating engine writes it — "4 vCPU-hours at $0.0100, less 5% Pro discount" — precisely so that support can answer "why is this line what it is" without re-running the engine against prices that may since have changed. It was stored from the start and had nowhere to go: without this field the column is write-only, and the question it exists to answer can only be answered from psql.
Empty on entries with nothing to derive, such as a plan grant.
Which balance this entry moved.
Load-bearing for balance_after, which is denormalised so a statement line
needs no running total computed client-side — and which now means "the
balance of this entry's purse" rather than "the balance". A statement
rendering two purses interleaved without reading this shows a balance that
appears to jump back and forth, which reads as a bug in the ledger.
Unset means the general purse, and every entry written before purses existed is one.
PURSE_GENERALPURSE_PROJECT{ "name": "accounts//ledgerEntries/my-ledger-entry", "account": "accounts/", "type": "TYPE_PLAN_GRANT", "amount": { "currency_code": "USD", "units": "5", "nanos": 0 }, "balance_after": { "currency_code": "USD", "units": "5", "nanos": 0 }, "description": "", "sku": "", "project": "projects/", "resource": "", "usage_interval": { "start_time": "2026-10-01T00:00:00Z", "end_time": "2026-10-04T00:00:00Z" }, "quantity_micros": "0", "create_time": "2026-10-04T12:00:00Z", "expire_time": "2026-10-04T12:00:00Z", "reverses_entry": "", "actor": "", "idempotency_key": "", "explanation": "", "purse": "PURSE_GENERAL"}PurseBalance is one of an account's credit balances.
pursePursePURSE_GENERALPURSE_PROJECTbalanceMoneySpendable credit in this purse. Never negative except for the tail of one
in-flight workload — see Purse.
period_spendMoneySpend against this purse in the current billing period.
projectstringprojects/{project}, for a PURSE_PROJECT balance. Empty otherwise.
{ "purse": "PURSE_GENERAL", "balance": { "currency_code": "USD", "units": "5", "nanos": 0 }, "period_spend": { "currency_code": "USD", "units": "5", "nanos": 0 }, "project": "projects/"}One bucket of aggregated spend.
keysrepeated stringDimension values in the order requested, e.g. ["skus/playpute-vcpu-hour", "projects/p1"].
costMoneyquantity_microsint64usage_unitstring{ "keys": [ "" ], "cost": { "currency_code": "USD", "units": "5", "nanos": 0 }, "quantity_micros": "0", "usage_unit": ""}What pays for a billing account's plan.
planstringThe plan the subscription is for.
stateSubscription.StateSTATE_ACTIVESTATE_PAST_DUESTATE_CANCELEDSTATE_INCOMPLETESTATE_TRIALINGcurrent_period_endTimestampWhen the current paid period ends, and the next renewal is due.
cancel_at_period_endboolCancelled, and running until current_period_end.
billing_intervalBillingIntervalMonthly or annual.
BILLING_INTERVAL_MONTHLYBILLING_INTERVAL_ANNUALtrial_end_timeTimestampWhen the free trial ends, while the subscription is in one.
{ "plan": "plans/my-plan", "state": "STATE_ACTIVE", "current_period_end": "2026-10-04T12:00:00Z", "cancel_at_period_end": false, "billing_interval": "BILLING_INTERVAL_MONTHLY", "trial_end_time": "2026-10-04T12:00:00Z"}Price is a complete, ratable price for one SKU in one currency.
PRICING_TYPE_PER_HOURPRICING_TYPE_PER_UNIT_HOURPRICING_TYPE_PER_UNIT_MONTHPRICING_TYPE_PER_UNIT_ONCEPRICING_TYPE_PER_REQUESTPRICING_TYPE_PER_GIB_TRANSFERPRICING_TYPE_FLAT_MONTHLYPRICING_TYPE_FREEusage_unitstringRequiredThe unit quantity is measured in, for display and for sanity-checking
emitters: "vcpu-hour", "gib-hour", "gib", "request", "hour", "month".
unit_priceMoneyPrice per usage_unit. Ignored when tiering_mode is GRADUATED or VOLUME.
tiering_modeTieringModeTIERING_MODE_FLATTIERING_MODE_GRADUATEDTIERING_MODE_VOLUMEtiersrepeated PricingTierBands, ascending by start_quantity. Only read for GRADUATED and VOLUME.
start_quantityint64RequiredInclusive lower bound of this band, in the SKU's usage unit.
Price for one unit within this band. Uses google.type.Money's units/nanos, giving nine decimal places — enough for rates like $0.000011 per vCPU second without rounding drift.
free_quantity_per_monthint64Quantity granted free per account per calendar month before charging begins. This is how the free tier is expressed per-SKU rather than as a special case in the rating engine.
minimum_billable_quantityint64Smallest quantity that can be billed, in usage_unit. A VM that lives for 40 seconds against a 60-second minimum is billed for 60. Guards against create/destroy loops costing more to meter than they earn.
{ "pricing_type": "PRICING_TYPE_PER_HOUR", "usage_unit": "", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }, "tiering_mode": "TIERING_MODE_FLAT", "tiers": [ { "start_quantity": "0", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 } } ], "free_quantity_per_month": "0", "minimum_billable_quantity": "0"}PricingTier is one band of a graduated or volume price.
Tiers are ordered by start_quantity ascending and the first tier must start at zero. With graduated tiering, quantity within each band is charged at that band's rate; with volume tiering, the whole quantity is charged at the rate of the band it lands in.
start_quantityint64RequiredInclusive lower bound of this band, in the SKU's usage unit.
Price for one unit within this band. Uses google.type.Money's units/nanos, giving nine decimal places — enough for rates like $0.000011 per vCPU second without rounding drift.
{ "start_quantity": "0", "unit_price": { "currency_code": "USD", "units": "5", "nanos": 0 }}period and spend controls.
namestringIdentifieraccounts/{account}/billing.
planstringOutput onlyCurrent plan, plans/{plan}.
Spendable credit. Never negative: usage that would overdraw stops the workload instead.
Charges accrued but not yet posted, from the current partial hour. Shown so the console's balance does not appear to jump each hour.
Current billing period, when the plan grant lands and when it expires.
exhaustion_policyBillingAccount.ExhaustionPolicyEXHAUSTION_POLICY_STOP_WORKLOADSEXHAUSTION_POLICY_AUTO_TOPUPauto_topup_amountMoneyAmount to buy when auto top-up triggers.
auto_topup_thresholdMoneyBalance at which auto top-up triggers.
spend_capMoneyHard ceiling on spend per billing period. Once reached, workloads stop even if credit remains — the guard rail that lets a studio hand a project to a contractor without unbounded exposure. Zero means no cap.
alert_thresholdsrepeated MoneyBalances at which to notify the account, e.g. $5 and $1. Each fires once per period.
payment_method_validboolOutput onlyWhether a usable payment method is on file.
currency_codestringOutput onlyISO 4217 currency for this account. Fixed at creation; all Money on the account uses it.
Observed burn rate over the last 24 hours, used to project a runout date and to warn before the balance actually hits zero.
Projected exhaustion at the current burn rate. The single most useful number to put in front of a customer.
Every balance this account holds, including the general one.
balance above stays the general purse and keeps meaning exactly what
it always meant, because every existing caller reads it and none of them
knows about purses. A console showing one number is showing the number it
has always shown.
pursePursePURSE_GENERALPURSE_PROJECTbalanceMoneySpendable credit in this purse. Never negative except for the tail of one
in-flight workload — see Purse.
period_spendMoneySpend against this purse in the current billing period.
projectstringprojects/{project}, for a PURSE_PROJECT balance. Empty otherwise.
The Stripe subscription that pays for the plan, when one does.
Absent for an account whose plan credit Plany grants without charging — every account opened before plans were sold, and every invited one. Present and not ACTIVE is a plan whose monthly credit has stopped: the renewal failed or the subscription ended, and purchased credit is all the account is running on.
planstringThe plan the subscription is for.
stateSubscription.StateSTATE_ACTIVESTATE_PAST_DUESTATE_CANCELEDSTATE_INCOMPLETESTATE_TRIALINGcurrent_period_endTimestampWhen the current paid period ends, and the next renewal is due.
cancel_at_period_endboolCancelled, and running until current_period_end.
billing_intervalBillingIntervalMonthly or annual.
BILLING_INTERVAL_MONTHLYBILLING_INTERVAL_ANNUALtrial_end_timeTimestampWhen the free trial ends, while the subscription is in one.
trial_availableboolOutput onlyWhether a subscription opened now would start with the plan's free trial. True only for an account that has never had a subscription, so a trial is had once per account.
A Customer Portal session.
urlstringOutput onlyStripe's portal page for this account. Short-lived and single-use; send the browser to it immediately rather than storing it.
A Stripe Checkout session for credit.
namestringOutput onlyaccounts/{account}/checkoutSessions/{session}, the session id being
Stripe's.
client_secretstringOutput onlyWhat mounts embedded Checkout in the browser. Present while the session is open; a session that has completed or expired has nothing to mount.
The credit being bought, before tax.
projectstringOutput onlyThe top-up project the credit is for, when it is for one.
STATE_OPENSTATE_PROCESSINGSTATE_PAIDSTATE_EXPIREDledger_entrystringOutput onlyThe grant this payment produced, once it has.
planstringOutput onlyThe plan being subscribed to, for a subscription session. amount is
then the plan's monthly price, and ledger_entry stays empty: a plan's
credit is granted per invoice, not per session.
trial_daysint32Output onlyDays of free trial the subscription starts with, when it does. amount
is then charged at the trial's end rather than now.
How often the plan this session subscribes to is paid for.
BILLING_INTERVAL_MONTHLYBILLING_INTERVAL_ANNUALledger_entriesrepeated LedgerEntrynamestringIdentifieraccountstringOutput onlyTYPE_PLAN_GRANTTYPE_PURCHASETYPE_PROMOTIONAL_GRANTTYPE_USAGE_CHARGETYPE_REFUNDTYPE_ADJUSTMENTTYPE_EXPIRYTYPE_REVERSALTYPE_PLAYER_TOP_UPSigned: positive adds credit, negative spends it.
Balance immediately after this entry, so a statement line needs no running total computed client-side.
descriptionstringOutput onlyskustringOutput onlyFor a USAGE_CHARGE, what it paid for.
projectstringOutput onlyresourcestringOutput onlyquantity_microsint64Output onlyWhen a grant's unspent remainder expires. Grants are consumed expiry-soonest first, so plan credit is spent before purchased credit.
reverses_entrystringOutput onlyEntry this reverses, for REVERSAL and REFUND.
actorstringOutput onlyPrincipal responsible, for ADJUSTMENT and PURCHASE.
idempotency_keystringOutput onlyThe key that produced this entry. Unique per account, so the same usage is never charged twice.
explanationstringOutput onlyHow the charge was arrived at, in words.
The rating engine writes it — "4 vCPU-hours at $0.0100, less 5% Pro discount" — precisely so that support can answer "why is this line what it is" without re-running the engine against prices that may since have changed. It was stored from the start and had nowhere to go: without this field the column is write-only, and the question it exists to answer can only be answered from psql.
Empty on entries with nothing to derive, such as a plan grant.
Which balance this entry moved.
Load-bearing for balance_after, which is denormalised so a statement line
needs no running total computed client-side — and which now means "the
balance of this entry's purse" rather than "the balance". A statement
rendering two purses interleaved without reading this shows a balance that
appears to jump back and forth, which reads as a bug in the ledger.
Unset means the general purse, and every entry written before purses existed is one.
PURSE_GENERALPURSE_PROJECTnext_page_tokenstringplansrepeated PlannamestringIdentifierplans/{plan} — plans/hobby or plans/pro.
display_namestringOutput onlydescriptionstringOutput onlyCharged monthly: $5 for Hobby, $20 for Pro.
Credit granted at the start of each billing period. Equal to monthly_price on both current plans, so the subscription fee is entirely spendable and the plan is effectively a prepaid bundle rather than a fee on top of usage.
credit_rolls_overboolOutput onlyWhether unspent included credit carries into the next period. False on both launch plans: included credit expires, purchased credit does not.
Cap on rolled-over credit, when rollover is enabled.
usage_discount_basis_pointsint32Output onlyDiscount applied to usage rates while on this plan, in basis points (500 = 5% off). Zero on Hobby; Pro trades a higher fee for cheaper usage.
plan_idstringOutput onlyPlan id used as the key in QuotaMetric.plan_defaults, tying plans to the quota model without either service importing the other's storage.
availableboolOutput onlyWhether new accounts may select this plan. A retired plan stays readable so existing subscribers still resolve.
support_tierstringOutput onlySupport response target, for display on the pricing page.
Charged once a year when the plan is paid annually. Absent for a plan that has no annual variant. $200 for Pro.
Credit granted at the start of each month of an annual subscription —
more than included_credit, which is part of what paying a year ahead
buys. $25 for Pro. Absent when annual_price is.
trial_daysint32Output onlyDays of free trial an account's first subscription to this plan starts
with. The trial's period is granted the plan's included_credit, expiring
when the trial ends. 7 on Hobby; zero on a plan with no trial.
skusrepeated Skudisplay_namestringRequiredservicestringRequiredThe product that owns this SKU, e.g. secretmanager.plany.cloud. Groups line items on a statement.
Default price, used where no location override applies.
PRICING_TYPE_PER_HOURPRICING_TYPE_PER_UNIT_HOURPRICING_TYPE_PER_UNIT_MONTHPRICING_TYPE_PER_UNIT_ONCEPRICING_TYPE_PER_REQUESTPRICING_TYPE_PER_GIB_TRANSFERPRICING_TYPE_FLAT_MONTHLYPRICING_TYPE_FREEusage_unitstringRequiredThe unit quantity is measured in, for display and for sanity-checking
emitters: "vcpu-hour", "gib-hour", "gib", "request", "hour", "month".
unit_priceMoneyPrice per usage_unit. Ignored when tiering_mode is GRADUATED or VOLUME.
tiering_modeTieringModeTIERING_MODE_FLATTIERING_MODE_GRADUATEDTIERING_MODE_VOLUMEtiersrepeated PricingTierBands, ascending by start_quantity. Only read for GRADUATED and VOLUME.
free_quantity_per_monthint64Quantity granted free per account per calendar month before charging begins. This is how the free tier is expressed per-SKU rather than as a special case in the rating engine.
minimum_billable_quantityint64Smallest quantity that can be billed, in usage_unit. A VM that lives for 40 seconds against a 60-second minimum is billed for 60. Guards against create/destroy loops costing more to meter than they earn.
namestringIdentifierskus/{sku}, where the id is stable and readable, e.g.
skus/microvm-vcpu-hour-standard.
descriptionstringcategorystringCoarse family for reporting: "compute", "storage", "network", "support".
location_pricesmap<string, Price>Per-location price overrides keyed by location id ("eu-west-2"). Regions genuinely cost different amounts to run, and this keeps that visible rather than averaged into one global rate.
PRICING_TYPE_PER_HOURPRICING_TYPE_PER_UNIT_HOURPRICING_TYPE_PER_UNIT_MONTHPRICING_TYPE_PER_UNIT_ONCEPRICING_TYPE_PER_REQUESTPRICING_TYPE_PER_GIB_TRANSFERPRICING_TYPE_FLAT_MONTHLYPRICING_TYPE_FREEusage_unitstringRequiredThe unit quantity is measured in, for display and for sanity-checking
emitters: "vcpu-hour", "gib-hour", "gib", "request", "hour", "month".
unit_priceMoneyPrice per usage_unit. Ignored when tiering_mode is GRADUATED or VOLUME.
tiering_modeTieringModeTIERING_MODE_FLATTIERING_MODE_GRADUATEDTIERING_MODE_VOLUMEtiersrepeated PricingTierBands, ascending by start_quantity. Only read for GRADUATED and VOLUME.
free_quantity_per_monthint64Quantity granted free per account per calendar month before charging begins. This is how the free tier is expressed per-SKU rather than as a special case in the rating engine.
minimum_billable_quantityint64Smallest quantity that can be billed, in usage_unit. A VM that lives for 40 seconds against a 60-second minimum is billed for 60. Guards against create/destroy loops costing more to meter than they earn.
quota_metricstringQuota metric this SKU's usage also counts against, if any. Links the "how much may I have" and "what does it cost" models.
effective_end_timeTimestampWhen this SKU stops being ratable. Usage after this is rejected rather than silently priced at zero.
publicboolWhether the SKU is visible on the public price list. Internal SKUs cover costs Plany absorbs and are hidden.
next_page_tokenstringPlan is a subscription tier. The brief defines two.
namestringIdentifierplans/{plan} — plans/hobby or plans/pro.
display_namestringOutput onlydescriptionstringOutput onlyCharged monthly: $5 for Hobby, $20 for Pro.
Credit granted at the start of each billing period. Equal to monthly_price on both current plans, so the subscription fee is entirely spendable and the plan is effectively a prepaid bundle rather than a fee on top of usage.
credit_rolls_overboolOutput onlyWhether unspent included credit carries into the next period. False on both launch plans: included credit expires, purchased credit does not.
Cap on rolled-over credit, when rollover is enabled.
usage_discount_basis_pointsint32Output onlyDiscount applied to usage rates while on this plan, in basis points (500 = 5% off). Zero on Hobby; Pro trades a higher fee for cheaper usage.
plan_idstringOutput onlyPlan id used as the key in QuotaMetric.plan_defaults, tying plans to the quota model without either service importing the other's storage.
availableboolOutput onlyWhether new accounts may select this plan. A retired plan stays readable so existing subscribers still resolve.
support_tierstringOutput onlySupport response target, for display on the pricing page.
Charged once a year when the plan is paid annually. Absent for a plan that has no annual variant. $200 for Pro.
Credit granted at the start of each month of an annual subscription —
more than included_credit, which is part of what paying a year ahead
buys. $25 for Pro. Absent when annual_price is.
trial_daysint32Output onlyDays of free trial an account's first subscription to this plan starts
with. The trial's period is granted the plan's included_credit, expiring
when the trial ends. 7 on Hobby; zero on a plan with no trial.
Sku is a priced, meterable thing. Services register their own SKUs, which is how "resources/services support per-hour billing with the ability to set their own pricing types" is delivered without billing knowing about them.
display_namestringRequiredservicestringRequiredThe product that owns this SKU, e.g. secretmanager.plany.cloud. Groups line items on a statement.
Default price, used where no location override applies.
PRICING_TYPE_PER_HOURPRICING_TYPE_PER_UNIT_HOURPRICING_TYPE_PER_UNIT_MONTHPRICING_TYPE_PER_UNIT_ONCEPRICING_TYPE_PER_REQUESTPRICING_TYPE_PER_GIB_TRANSFERPRICING_TYPE_FLAT_MONTHLYPRICING_TYPE_FREEusage_unitstringRequiredThe unit quantity is measured in, for display and for sanity-checking
emitters: "vcpu-hour", "gib-hour", "gib", "request", "hour", "month".
unit_priceMoneyPrice per usage_unit. Ignored when tiering_mode is GRADUATED or VOLUME.
tiering_modeTieringModeTIERING_MODE_FLATTIERING_MODE_GRADUATEDTIERING_MODE_VOLUMEtiersrepeated PricingTierBands, ascending by start_quantity. Only read for GRADUATED and VOLUME.
start_quantityint64RequiredInclusive lower bound of this band, in the SKU's usage unit.
Price for one unit within this band. Uses google.type.Money's units/nanos, giving nine decimal places — enough for rates like $0.000011 per vCPU second without rounding drift.
free_quantity_per_monthint64Quantity granted free per account per calendar month before charging begins. This is how the free tier is expressed per-SKU rather than as a special case in the rating engine.
minimum_billable_quantityint64Smallest quantity that can be billed, in usage_unit. A VM that lives for 40 seconds against a 60-second minimum is billed for 60. Guards against create/destroy loops costing more to meter than they earn.
namestringIdentifierskus/{sku}, where the id is stable and readable, e.g.
skus/microvm-vcpu-hour-standard.
descriptionstringcategorystringCoarse family for reporting: "compute", "storage", "network", "support".
location_pricesmap<string, Price>Per-location price overrides keyed by location id ("eu-west-2"). Regions genuinely cost different amounts to run, and this keeps that visible rather than averaged into one global rate.
PRICING_TYPE_PER_HOURPRICING_TYPE_PER_UNIT_HOURPRICING_TYPE_PER_UNIT_MONTHPRICING_TYPE_PER_UNIT_ONCEPRICING_TYPE_PER_REQUESTPRICING_TYPE_PER_GIB_TRANSFERPRICING_TYPE_FLAT_MONTHLYPRICING_TYPE_FREEusage_unitstringRequiredThe unit quantity is measured in, for display and for sanity-checking
emitters: "vcpu-hour", "gib-hour", "gib", "request", "hour", "month".
unit_priceMoneyPrice per usage_unit. Ignored when tiering_mode is GRADUATED or VOLUME.
tiering_modeTieringModeTIERING_MODE_FLATTIERING_MODE_GRADUATEDTIERING_MODE_VOLUMEtiersrepeated PricingTierBands, ascending by start_quantity. Only read for GRADUATED and VOLUME.
start_quantityint64RequiredInclusive lower bound of this band, in the SKU's usage unit.
Price for one unit within this band. Uses google.type.Money's units/nanos, giving nine decimal places — enough for rates like $0.000011 per vCPU second without rounding drift.
free_quantity_per_monthint64Quantity granted free per account per calendar month before charging begins. This is how the free tier is expressed per-SKU rather than as a special case in the rating engine.
minimum_billable_quantityint64Smallest quantity that can be billed, in usage_unit. A VM that lives for 40 seconds against a 60-second minimum is billed for 60. Guards against create/destroy loops costing more to meter than they earn.
quota_metricstringQuota metric this SKU's usage also counts against, if any. Links the "how much may I have" and "what does it cost" models.
effective_end_timeTimestampWhen this SKU stops being ratable. Usage after this is rejected rather than silently priced at zero.
publicboolWhether the SKU is visible on the public price list. Internal SKUs cover costs Plany absorbs and are hidden.
How the account behaves as the balance nears zero.
| Value | Description |
|---|---|
| EXHAUSTION_POLICY_UNSPECIFIED | |
| EXHAUSTION_POLICY_STOP_WORKLOADS | Stop workloads, keep data. The default and the only option for an account with no payment method. |
| EXHAUSTION_POLICY_AUTO_TOPUP | Charge the payment method for a top-up and keep running. What a studio wants; requires a verified card. |
How often a subscription is paid for.
| Value | Description |
|---|---|
| BILLING_INTERVAL_UNSPECIFIED | |
| BILLING_INTERVAL_MONTHLY | Paid each month; each paid month grants `Plan.included_credit`. |
| BILLING_INTERVAL_ANNUAL | Paid a year ahead; each month of that year grants `Plan.annual_included_credit`. |
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_OPEN | Waiting for the customer. |
| STATE_PROCESSING | The customer finished and the payment has not settled yet — a bank debit, for one. The credit arrives when it does. |
| STATE_PAID | Paid. `ledger_entry` is set once the credit has been granted, which is the webhook's doing and can trail this by a moment. |
| STATE_EXPIRED | Closed without payment. |
| Value | Description |
|---|---|
| TYPE_UNSPECIFIED | |
| TYPE_PLAN_GRANT | Monthly plan credit granted. |
| TYPE_PURCHASE | Credit bought with a card or on invoice. |
| TYPE_PROMOTIONAL_GRANT | Promotional or goodwill credit. |
| TYPE_USAGE_CHARGE | Usage charged. The overwhelmingly common entry type. |
| TYPE_REFUND | Money returned to the customer's payment method. |
| TYPE_ADJUSTMENT | Operator correction, always with a reason. |
| TYPE_EXPIRY | Unspent non-rolling grant removed at period end. |
| TYPE_REVERSAL | Charge reversed, e.g. after a failed provision that was still metered. |
| TYPE_PLAYER_TOP_UP | Credit a player bought for a project, through a game's own store. Distinct from `TYPE_PURCHASE` because the person who paid is not the account holder and did not pay Plany: they paid a distributor, the developer was paid by the distributor, and Plany is owed by the developer. A statement that filed it as a purchase would read as a receipt for money the account holder never spent. Always in a project purse. See `plany.encore.v1`. |
Purse is which of an account's credit balances something draws on.
Almost everything draws on the general balance, which is where plan credit and top-ups go. PURSE_UNSPECIFIED means the general balance: it is a real answer rather than an error, and it is what you will see on nearly every entry.
When the general balance is exhausted the account's workloads stop and their data is kept for 90 days, with a weekly reminder, before it is deleted.
| Value | Description |
|---|---|
| PURSE_UNSPECIFIED | The general balance: plan credit and purchased credit. Unset means this. |
| PURSE_GENERAL | Explicitly the general balance, for a caller that wants to say so. |
| PURSE_PROJECT | One project's own balance, for a project that is top-up only. Every other purse is keyed by *what* is being bought; this one is keyed by *where*, because the credit in it was paid for a particular project — by that game's players, through Encore — and spending it anywhere else would be spending somebody else's money. A project purse therefore draws every SKU charged inside its project, and no plan credit, allowance or discount ever reaches it. Never meaningful on its own: whatever carries it also carries the project. Exhausting it stops that project's workloads and suspends nothing else. |
Dimension to break spend down by.
| Value | Description |
|---|---|
| GROUP_BY_UNSPECIFIED | |
| GROUP_BY_SKU | |
| GROUP_BY_PROJECT | |
| GROUP_BY_SERVICE | |
| GROUP_BY_LOCATION | |
| GROUP_BY_RESOURCE | |
| GROUP_BY_LABEL | |
| GROUP_BY_DAY | |
| GROUP_BY_HOUR |
| Value | Description |
|---|---|
| STATE_UNSPECIFIED | |
| STATE_ACTIVE | Paid up. Each renewal grants the period's credit. |
| STATE_PAST_DUE | A renewal failed and Stripe is retrying it. No credit is granted for the period until it is paid. |
| STATE_CANCELED | Ended — cancelled, or every retry failed. No more plan credit; purchased credit keeps working. |
| STATE_INCOMPLETE | Created and never paid. |
| STATE_TRIALING | In its free trial, which ends at `trial_end_time`. The trial's credit is granted; the first charge is at the trial's end. |
PricingType is how a SKU converts usage into money.
Every billable thing on Plany registers a SKU that names one of these. The brief calls for per-hour billing as the baseline with services free to choose their own pricing type, so the rating engine switches on this enum and nothing else — a new service becomes billable by inserting a SKU row, never by changing billing code.
| Value | Description |
|---|---|
| PRICING_TYPE_UNSPECIFIED | |
| PRICING_TYPE_PER_HOUR | Flat charge for each hour the resource exists, regardless of size. quantity is hours. Example: a static IPv4 reservation. |
| PRICING_TYPE_PER_UNIT_HOUR | Charge per unit per hour. quantity is unit-hours. This is the workhorse: vCPU-hours, GiB-of-RAM-hours, GiB-of-disk-hours. |
| PRICING_TYPE_PER_UNIT_MONTH | Charge per unit per month, prorated to the second over the calendar month's actual length. quantity is unit-months. |
| PRICING_TYPE_PER_UNIT_ONCE | One-off charge per unit, not time-based. quantity is units. Example: a snapshot export or a domain registration. |
| PRICING_TYPE_PER_REQUEST | Charge per API request or per operation. quantity is requests. Normally priced per million with a large free allowance. |
| PRICING_TYPE_PER_GIB_TRANSFER | Charge per GiB transferred. quantity is GiB. Egress bandwidth, the line item that most often surprises game hosting customers, so it gets its own type rather than hiding inside PER_UNIT_ONCE. |
| PRICING_TYPE_FLAT_MONTHLY | Flat charge each month for the resource's existence, prorated on create and delete. quantity is months. |
| PRICING_TYPE_FREE | Tracked for reporting and quota, and never charged. |
TieringMode selects how PricingTier bands combine.
| Value | Description |
|---|---|
| TIERING_MODE_UNSPECIFIED | |
| TIERING_MODE_FLAT | No tiers; unit_price applies to the whole quantity. |
| TIERING_MODE_GRADUATED | Each band charges its own rate for the quantity falling inside it. |
| TIERING_MODE_VOLUME | The band containing the total quantity sets the rate for all of it. |