Billing & Plans

Roast subscriptions are managed through Stripe. From Settings → Billing you can see your current plan and usage, upgrade or downgrade between the Spark, Flame and Inferno plans, and open the Stripe customer portal to manage payment details, invoices and cancellation.

Where to find billing

The billing screen lives at Dashboard → Settings → Billing (/dashboard/settings/billing). It shows your current plan, this month's usage, the full plans grid, and your invoice history.

The older /dashboard/billing URL still exists but only as a redirect: it forwards you (preserving any query parameters such as ?success=true) to /dashboard/settings/billing. Stripe checkout and portal flows return to these dashboard URLs after you finish.

All billing API actions require you to be signed in. Unauthenticated requests are rejected with 401 Unauthorized.

Billing in Roast is tied to the individual user account (roast_users), keyed by your Stripe customer ID — not to a separate workspace billing entity. Plan limits are read from the workspace owner's account when team members act.

Plans and limits

Roast has three plans. Prices shown are in US dollars (the billing screen renders amounts with a $ sign). Yearly billing is advertised at a ~17% saving versus monthly.

Spark (free, $0): 1 widget, 10 submissions per month, text feedback only, Roast watermark shown, 1 team member. Video, screen recording and AI analytics are disabled.

Flame ($29/month or $290/year): unlimited widgets, 500 submissions per month, video and screen recording, watermark removed, AI analytics, up to 5 team members, and integrations (Slack, Zapier). This is flagged as the most popular plan.

Inferno ($99/month or $990/year): everything in Flame plus unlimited submissions, unlimited team members, white-label branding, SSO/SAML, API access, priority support and custom integrations.

In the code, a limit of -1 means unlimited. These limits are defined in the PLANS configuration in src/lib/billing/stripe.ts and are the values actually enforced by the server.

Limits are enforced server-side, not just shown in the UI. Creating widgets is blocked once you reach your plan's widget limit — the forms API returns 403 'Plan limit reached. Upgrade to create more widgets.' (src/app/api/forms/route.ts).

AI analysis of a submission is blocked on Spark — the analyze endpoint returns 403 'AI analysis is not available on your current plan. Upgrade to Flame or Inferno to unlock AI insights.' (src/app/api/submissions/[submissionId]/analyze/route.ts).

The yearly toggle on the billing page only changes the displayed price; checkout always uses the plan's single configured Stripe price ID, so the monthly/yearly switch does not currently select a separate annual price.

Viewing your current plan and usage

The current-plan card shows your plan name and, if you have an active paid subscription, the renewal date (formatted as e.g. 15 June 2026). If a cancellation is scheduled, it shows '(Cancels at period end)'. On the free plan it shows 'Free plan — upgrade to unlock more features'.

Two usage meters are shown: Submissions This Month and Widgets. Submissions are counted from the first of the current month onward across all your widgets; widgets are a count of your forms. The submissions meter shows a progress bar against your plan limit (or '/ Unlimited' on Inferno).

Usage figures come from GET /api/billing/status, which counts roast_forms and roast_submissions directly from the database, so they reflect live data rather than Stripe metering.

If Roast still references a Stripe subscription that has been deleted on Stripe's side, the status endpoint detects the 'resource_missing' error, clears the stale subscription ID, and treats you as being on the free Spark plan.

Upgrading or changing plan

Behind the scenes, checkout looks up your stored Stripe customer ID (stripe_customer_id on your roast_users row). If you don't have one yet, Roast creates a new Stripe customer and saves the ID immediately so retries never create duplicates. It then opens a subscription-mode Checkout session for the selected plan's price.

Checkout sessions are created with a 14-day free trial, card payment, required billing address, and promotion codes enabled — so you can apply a discount code at checkout.

  1. 1Go to Settings → Billing.
  2. 2Find the plan you want in the plans grid. Your current plan shows a disabled 'Current Plan' button. Paid plans show an 'Upgrade' button; the free plan shows a 'Downgrade' button.
  3. 3Click Upgrade (or Downgrade). Roast calls POST /api/billing/checkout, which creates a Stripe Checkout session and redirects your browser to Stripe's hosted checkout page.
  4. 4Enter payment details on Stripe and confirm. Stripe returns you to /dashboard/settings/billing?success=true on success, or ?cancelled=true if you abandon checkout.

Your account email is required for checkout. If your account has no email, checkout returns 400 'Email required for billing'.

A plan can only be purchased if its Stripe price ID is configured (the STRIPE_*_PRICE_ID environment variables). If not, checkout returns 400 'Plan not configured'.

Known issue (worth flagging to the Roast team): the billing page sends the selected plan as { planId } in the request body, but the checkout API reads { plan }. As written, the API would not receive a valid plan and would return 400 'Invalid plan'. If Upgrade buttons appear to do nothing, this mismatch in the request payload is the likely cause.

Managing billing via the Stripe portal

The portal is Stripe's own hosted billing management surface. Cancellation, plan changes and card updates made there are reflected back into Roast via Stripe webhooks (see below).

  1. 1On the current-plan card, click 'Manage Billing'. This button only appears when you are on a paid plan (not Spark).
  2. 2Roast calls POST /api/billing/portal, which creates a Stripe Customer Portal session and redirects you to it.
  3. 3In the portal you can update payment methods, change or cancel your subscription, and download invoices. When you're done, Stripe returns you to the dashboard.

If your account has no Stripe customer ID on file, the portal endpoint returns 400 'No billing account found'. In practice this means you've never started a checkout, so there's nothing to manage.

The portal return URL points at /dashboard/billing, which then redirects to /dashboard/settings/billing.

Invoices and payment history

On a paid plan, the Invoice History table lists up to your 10 most recent Stripe invoices, showing the invoice number, date, amount, status (Paid, Open, or other) and a download link. Amounts are taken from Stripe's amount_paid; the download opens Stripe's hosted invoice page in a new tab.

On the free Spark plan with no invoices, you'll see a 'No billing history' panel inviting you to upgrade to access invoices and payment management.

Invoice data is fetched live from Stripe by GET /api/billing/status. If Stripe isn't configured, the list comes back empty rather than erroring.

How subscription changes take effect (webhooks)

Roast keeps your plan in sync with Stripe through a webhook at POST /api/billing/webhook. The webhook verifies Stripe's signature using STRIPE_WEBHOOK_SECRET and rejects requests with a missing or invalid signature.

On checkout.session.completed and on subscription created/updated events, Roast derives the plan from the subscription's price ID and updates your roast_users row with the new plan and subscription ID. On customer.subscription.deleted, your account is set back to the free Spark plan and the subscription ID is cleared.

On invoice.payment_failed, Roast looks up the affected user and sends a 'payment_failed' notification via email and in-app channels. invoice.payment_succeeded is acknowledged but takes no further action.

Plan changes you make in Stripe Checkout or the customer portal become active in Roast once the corresponding webhook is processed, so there may be a brief delay before the dashboard reflects the new plan.

Because the plan is mapped from the Stripe price ID, the STRIPE_FLAME_PRICE_ID and STRIPE_INFERNO_PRICE_ID environment variables must be set correctly or paid subscriptions will fall back to Spark.

Frequently asked questions

Which plans are available and what do they cost?
Spark (free), Flame ($29/month or $290/year) and Inferno ($99/month or $990/year). Spark gives 1 widget and 10 submissions/month with text-only feedback and a watermark; Flame adds unlimited widgets, 500 submissions/month, video, screen recording, AI analytics, watermark removal and 5 team members; Inferno adds unlimited submissions and team members, white-label, SSO/SAML, API access and priority support.
How do I upgrade my plan?
Go to Settings → Billing, click Upgrade on the plan you want, then complete payment on Stripe's checkout page. You're returned to the billing page with a success indicator. New subscriptions include a 14-day free trial and you can apply a promotion code at checkout.
How do I update my card, download invoices or cancel?
On a paid plan, click 'Manage Billing' on the current-plan card. This opens the Stripe customer portal where you can change payment methods, manage or cancel your subscription, and download invoices. The button is hidden on the free Spark plan.
What happens when I cancel?
Cancellations are handled in the Stripe portal. If you cancel at period end, the billing page shows '(Cancels at period end)' next to your renewal date, and you keep access until then. When the subscription is finally deleted, a Stripe webhook moves your account back to the free Spark plan.
Why can't I create another widget or run AI analysis?
You've hit a plan limit. Widget creation is blocked with a 403 once you reach your plan's widget cap, and AI analysis is disabled entirely on Spark. Upgrade to Flame or Inferno to lift these limits.
Is usage measured in real time?
Yes. Submissions-this-month and widget counts are read directly from the database for the current calendar month, so the meters on the billing page reflect live data, independent of Stripe's metering.
My Upgrade button does nothing — is that a bug?
Possibly. The billing page sends the chosen plan as 'planId' while the checkout API expects 'plan', which can cause checkout to be rejected as an invalid plan. If upgrades don't start a Stripe checkout, report this payload mismatch to the Roast team.