Skip to content

Subscription Tiers

SkyState offers three subscription tiers. Limits are configured in api/SkyState.Api/appsettings.json under the TierSettings key and loaded at startup via IOptions<TierSettings>.

Tier Comparison

LimitFreeHobbyPro
Max projects3715
Max API requests / month2002,00020,000
Environments per project3 fixed (development, staging, production)samesame
Stripe subscription requiredNoYesYes

null in the limit config means unlimited. The Pro tier could be configured with null limits in the future without code changes.

Tier Hierarchy

Tiers are ordered internally: free (0) < hobby (1) < pro (2). This ordering is used to determine whether a tier change is an upgrade or a downgrade:

  • Upgrade (e.g., free → hobby, hobby → pro) — takes effect immediately; proration is charged.
  • Downgrade (e.g., pro → hobby) — scheduled via a Stripe Subscription Schedule to take effect at the end of the current billing period.

Grace Period

When a payment fails, the account enters a 7-day grace period. During the grace period:

  • Project creation limits are not enforced (CheckProjectLimitAsync returns Success(true))
  • The account retains its current subscription tier for the duration of the grace period
  • After 7 days without payment recovery, the subscription is cancelled by Stripe and the account is reset to the free tier

The grace period is implemented in BillingService using the payment_failed_at column on the account table.

Adding or Changing Tier Limits

Tier limits live entirely in configuration — no code changes are required to adjust them. In appsettings.json (or environment-specific overrides):

json
"TierSettings": {
  "Tiers": {
    "free": {
      "MaxProjects": 3,
      "MaxApiRequestsPerMonth": 200
    },
    "hobby": {
      "MaxProjects": 7,
      "MaxApiRequestsPerMonth": 2000
    },
    "pro": {
      "MaxProjects": 15,
      "MaxApiRequestsPerMonth": 20000
    }
  }
}

To add a new tier, you would also need to update the subscription_tier check constraint in the database schema and the GetNextTier logic in BillingService.

Database Representation

The active tier is stored in account.subscription_tier as a lowercase string. The database enforces a check constraint:

sql
check (subscription_tier in ('free', 'hobby', 'pro'))

The tier is updated by the Stripe webhook handler when a subscription event is received.

Built with VitePress