TurboPanel Docs
Deployment

First-run tier catalogue

A fresh TurboPanel High Availability control plane has a billing surface and no catalogue. The ladder (S1–S7, SX) is fixed in code; what a superadmin enters is only which Stripe product each priced label sells through. Nothing seeds this and nothing writes it automatically — by design, so a wrong price can never appear because a script ran. This page is that procedure, once per instance.

Self-hosted control planes skip all of it: every server they license is SX, free — see License tiers.

Before you start

You needWhere
A superadmin sessionSee The first admin — a freshly migrated TurboPanel High Availability database has none
TURBOPANEL_STRIPE_SECRET_KEY and TURBOPANEL_STRIPE_WEBHOOK_SIGNING_SECRET on the environmentCloudflare deploy settings for that env
A Stripe Tax default or tax_behavior set on every priceStripe Dashboard → Settings → Tax. The verifier refuses a price whose tax behaviour resolves to nothing

What an empty control plane looks like

Before anyone has entered anything: Admin → Tiers lists no rows; GET /api/admin/v1/tiers returns an empty list; an organization trying to buy a license sees no purchasable tier; a server that enrols is refused at enrol with License tier not assigned (an organization that has bought nothing). None of that is an error — it is the catalogue waiting to be typed in.

Step 1 — create seven products in Stripe

One product per priced label, S1 through S7. SX has no product: it is negotiated per customer and never sold through Checkout. For each product:

SettingValueWhy
Activeyesarchived products are refused
Default priceone recurring price, monthly, interval count 1, billing scheme per_unit, currency usdthe verifier checks every one of these
Unit amountthe ladder's list price for that label (table below), in centsa product whose default price disagrees with the ladder is refused for that label
Metadata turbopanel_tierthe label, e.g. S3the verifier reads it to confirm the product is the one you meant
Tax behaviourset on the price, or covered by the account defaultsee Before you start
LabelCores ≤RAM ≤List price / month
S1416 GiB$5.00
S21032 GiB$7.50
S31664 GiB$10.00
S432128 GiB$15.00
S564256 GiB$20.00
S6128512 GiB$35.00
S72561 TiB$50.00

The prices are the ladder's — src/features/tiers/ladder.ts in the control plane — and the verifier holds Stripe to them. Changing a price is a code change first, then a Stripe change, never the other way round.

Step 2 — bind each product to its label

In Admin → Tiers, add a row per label and pick the product from the dropdown (it is GET /api/admin/v1/tiers/products — every active Stripe product with its default price, each already annotated with any verification failures). Binding runs the full verification for that label and refuses with the reasons if anything is off; a product can back exactly one label.

What the verifier refuses, in the words it uses:

  • product is archived (active=false) · product has no default price; set one in the Dashboard
  • default price is archived · default price type … ≠ recurring · recurring.interval … ≠ month · recurring.interval_count … ≠ 1 · billing_scheme … ≠ per_unit · currency … ≠ usd · default price has no unit_amount
  • no tax behaviour resolves for this price …
  • turbopanel_tier metadata … ≠ <label> and a unit amount that is not the label's list price

Step 3 — verify, then check the org side

Press Verify all (POST /api/admin/v1/tiers/verify): it re-verifies every priced row against Stripe and refreshes the cached price. Then, as an ordinary organization owner, open the billing page — all seven tiers should be purchasable at the ladder prices — and buy one S1 license in Stripe test mode before the control plane takes real money. A server whose cores and RAM fit S1 then enrols and connects; that is the whole path exercised.

Later changes

  • A price changes: edit ladder.ts, ship it, then change the Stripe default price; Verify all confirms the two agree again.
  • A product must be replaced: deactivate the row (POST /api/admin/v1/tiers/{id}/deactivate), bind the new product. Existing subscriptions keep their Stripe price until the next change.
  • SX: negotiated per customer; there is nothing to enter here.

Hand edits in the Stripe Dashboard

What you may and may not change in Stripe by hand, per Stripe account (testing, staging and live each have their own):

  • Do not edit customers' subscriptions or subscription schedules. TurboPanel creates, changes and releases them through the API (a license increase, a move, a decrease at the end of the period) and keeps its own record of pending changes and license counts beside them. A change made in the Dashboard never reaches that record, so licenses and what Stripe bills can end up out of step. Nothing reconciles hand edits yet: that is a planned feature, not a safety net. Make the change from the billing screens instead.
  • Do set dunning by hand, on purpose. These are account settings, not API fields: a custom retry schedule of about 14 days on staging and live (testing may use a much shorter one to exercise the lifecycle), and If all retries fail: cancel the subscription. That cancellation is the only thing that ends a past-due subscription; TurboPanel has no clock of its own and never cancels. Do not choose "leave past-due" or "mark unpaid", because the customer's licenses would then never end.
  • The catalogue is the exception in between. Products and prices are created by hand once (steps 1 and 2 above) and changed only the way Later changes describes, because the verifier holds them to the ladder in code.
  • Other account settings stay in the Dashboard: the Customer Portal's branding, and the Stripe Tax default behaviour and registrations. The portal's features are set by code, and a portal configuration edited in the Dashboard is never used.
Edit on GitHub

Last updated on

On this page