User guide

Plans and Entitlements

Looking for what it does rather than how to use it? Read the Plans and limits overview .

Last verified against the codebase: 2026-07-27. See “How this document is verified” at the end.


What it is

A plan (a ServiceTier) is a priced bundle of feature flags and numeric caps. An entitlement is what one account is allowed to do because of the plan it is on.

Every gate in Solidlio reads the same two JSON blobs on the plan the account resolves to — features and limits — so what a salesperson sells, what the platform admin edits, and what the API enforces are one value.


Concepts

ConceptWhat it is
Service tierOne row of the plan catalogue: name, prices, seat rules, features JSON, limits JSON. Ten are seeded.
Tier groupMSP_IT_PARTNER or end customer. An account may only subscribe within its own group.
Feature flagA key in features. Fifteen exist. Some are booleans, eight are graduated ladders, two are string arrays.
LadderAn ordered list of rungs for a graduated flag, e.g. changeManagement: VIEW_ONLY < BASIC < CAB < ADVANCED. A gate asks for a minimum rung.
LimitA numeric cap in limits. Twelve exist. -1 means unlimited, 0 means none.
Entitlement sourceWhich plan answered: the account’s own, or the managing MSP’s.
Upgrade envelopeThe structured upsell a blocked call returns instead of a bare refusal — which tier lifts the block, its price, and whether this caller may buy it.
Free floorThe zero-price tier an account drops to when its subscription stops paying: tier_msp_free or tier_customer_free.
Entitlement capsThree limits (maxOrganizations, maxUsersPerOrg, maxNestedDepth) that are also denormalised onto Account columns, because they are checked on hot paths.

The shared gate helpers are in @solidlio/databasefeatureSatisfies, ladderLevel, buildUpgradeEnvelope, buildLimitUpgradeEnvelope, applyTierToAccount.


The two plan families

An account is either an MSP/IT partner or an end customer, and each has its own five-rung ladder. The groups are not interchangeable: subscribing across groups is refused with TIER_GROUP_MISMATCH.

MSP / IT partnerEnd customer
MSP_FREECUSTOMER_FREE
MSP_STARTERCUSTOMER_ESSENTIALS
MSP_GROWTHCUSTOMER_PROFESSIONAL
MSP_SCALECUSTOMER_BUSINESS
MSP_ENTERPRISECUSTOMER_ENTERPRISE

Pricing shape. Free tiers are flat and free. Enterprise tiers are CUSTOM — no self-serve price, quote only. The three middle tiers in each group are HYBRID: a base platform fee plus a per-seat price for every seat beyond the included one.

monthly total = base + max(0, seats − includedSeats) × perSeatMonthly

Annual billing quotes a lower monthly-equivalent (for example MSP_GROWTH is $199/mo billed monthly, $165/mo billed annually).

Currency. The tier row holds the CAD price. A non-CAD price lives in a ServiceTierPrice row per currency. A tier with no row for the account’s currency is returned with priced: false and null prices rather than the CAD figure, and platform billing holds such an invoice as DRAFT instead of charging a number nobody agreed to.


Roles and permissions

ActionWho
Read your own entitlementsAny authenticated user, no role floor
Read the in-app plan catalogueorganization administrator+
See upgrade options and pricesorganization administrator+
Buy an upgradeorganization administrator+ (MSP portal: the upsell CTA appears at MSP administrator+)
Subscribe / change planorganization administrator+
Create, edit, price or delete a tierplatform administrator only
Assign a tier directly to an accountplatform administrator only

Two deliberate choices are worth calling out.

It describes what the account bought, not what the caller may do, and every role renders plan-gated UI — a CUSTOMER needs to know the change module is unavailable exactly as much as an organization administrator does. A floor here would blank the signal for non-admins and make the interface fail open on paid features. Note also that MSP technician ranks above organization administrator in the hierarchy, so the intuitive-looking floor would lock out the entire organization portal.

Self-serve is decided by the server. canSelfServe in the upgrade envelope is MSP administrator+ on the MSP portal and organization administrator+ everywhere else. The envelope is returned either way, so a user below the threshold still sees which plan unlocks the feature and is told to ask an administrator.

Managed clients buy their own plans. An MSP-managed client organization is its own tenant with its own account. Its admins use the same upgrade flow on their own account; the MSP does not have to broker it.


Walkthrough — choosing a plan at signup

  1. Finish signup. You land on Choose your plan (/onboarding/plan). The plan family shown follows the portal you signed up into: MSP partners see the MSP ladder, businesses see the customer ladder.
  2. Compare. Each card shows the base price, the per-seat price, included seats, the organization cap and the monthly AI credit allowance. Compare plans opens the full grid.
  3. Pick seats. For hybrid tiers, choose the seat count. The estimate updates as base + extra seats × per-seat.
  4. Choose monthly or annual. Annual quotes the discounted monthly equivalent and bills for twelve months.
  5. Confirm. Paid tiers with a trial start the trial (14 days on every paid non-Enterprise tier); paid tiers without one generate the first invoice immediately.
  6. Enterprise. Enterprise tiles route to Contact sales — they carry no self-serve price and cannot be subscribed to through the API.

If you are not an administrator, the page tells you plan changes need an administrator rather than showing a load error.


Walkthrough — upgrading

Upgrades are immediate and prorated. Downgrades are not self-serve.

  1. Open the upgrade surface. Either Manage your plan (/onboarding/plan), or the upgrade prompt that appears in place wherever a plan gate blocked you.
  2. Pick the target tier. Only higher-ranked tiers in your own group are offered, and Enterprise is excluded because it needs a quote.
  3. Check the prorated amount. You are charged the difference between the two tiers for the remaining fraction of the current billing period: round((newAmount − oldAmount) × fractionRemaining). With no active paid period (trial or free tier) the whole difference is charged. A lateral or downward move charges nothing.
  4. Pay. If a saved card can be charged off-session, the upgrade finalises inline. Otherwise you complete the payment and the upgrade confirms.
  5. Entitlements apply immediately. The new tier’s caps are pushed onto the account in the same transaction, so the feature you were blocked on works without waiting for a billing cycle.

Downgrading. Choosing a lower tier opens a support-contact panel. Downgrades are applied at the next renewal so you keep what you have paid for.


Walkthrough — editing a tier (platform admin)

Platform → Tiers (/platform/tiers).

  1. Pick a tier or create one. Each card shows price, seats, feature badges and limits, plus how many accounts and subscriptions are on it.
  2. Edit features. Booleans are toggles; graduated flags are dropdowns whose options are exactly the rungs the gates compare against.
  3. Edit limits. Every numeric cap. Two are labelled not enforcedmaxStorageGb and maxContacts are stored and displayed but no service reads them. Do not sell them as caps.
  4. Set non-CAD prices. Per-currency rows for USD and EUR. A tier with no row for a currency is shown as unpriced there and its invoices hold as DRAFT.
  5. Save. A PUT merges — keys you did not send are left alone. Every create, update, delete and price change writes a PlatformAuditLog entry with the before and after state.

Two safety rules are enforced for you:

  • includedSeats and minSeats may not exceed maxSeats, checked against the tier as it will be after the patch.
  • A tier in use by any account or subscription cannot be deleted.

⚠️ Re-running db:seed:reference overwrites features and limits on the ten seeded tiers. Tier tuning done here is not preserved across a reference re-seed. Per-currency prices are upserted, so only the currencies the seed declares are overwritten.


How a gate decides

Every plan gate follows the same four steps.

  1. Is this the platform account or a platform admin? If so, no gate applies. The platform account carries no tier at all and must never be plan-gated.
  2. Resolve the tier. The account’s own serviceTier if it has one, otherwise the managing MSP’s tier. A managed client frequently carries no plan of its own and is entitled by its MSP’s subscription.
  3. Compare. Booleans compare === true. Ladders compare rung indexes, so "CAB" satisfies a requirement of "BASIC". Arrays check membership. Numeric caps compare the current count against the cap, with -1 meaning unlimited.
  4. On a block, build the envelope and answer 403.

Gates block new work only. Reads, deletes and edits to existing records are not gated. A downgrade never makes existing data disappear or become unmanageable — it stops you creating more.

When no tier resolves. Most gates fail open: they allow the action rather than refuse an entitlement they could not confirm. The exceptions, which fail closed, are changeManagement, projectManagement, customBranding, whiteLabel, catalogControl, aiFeatures and maxAiCredits (the last drops to the free-floor credit allowance rather than to zero or to unlimited).


The upgrade envelope

A blocked call returns HTTP 403 with this body:

{
  "error": {
    "code": "FEATURE_NOT_ON_PLAN",
    "message": "SLA management isn't included in your current plan.",
    "upgrade": {
      "feature": "slaManagement",
      "requiredValue": "BASIC",
      "currentTier": "CUSTOMER_FREE",
      "requiredTier": "CUSTOMER_PROFESSIONAL",
      "requiredTierDisplayName": "Professional",
      "monthlyPriceCents": 10900,
      "yearlyPriceCents": 8900,
      "canSelfServe": true
    }
  }
}

The target tier is computed from the live catalogue — the cheapest tier in the caller’s own group whose features satisfy the requirement — so a platform-admin price or feature edit changes the offer immediately.

Codes.

CodeMeaning
FEATURE_NOT_ON_PLANThe plan lacks the feature, or holds too low a rung.
PLAN_LIMITA numeric cap is full (organizations, nesting, assets, contracts, active projects, technician seats, sending domains).
SEAT_LIMITThe per-organization user seat cap is full.
AI_FEATURE_NOT_ON_PLANThe plan’s aiFeatures allowlist excludes this AI capability.

All four are HTTP 403 with the same body shape. AI credit exhaustion is different: it answers 402 AI_CREDIT_LIMIT_EXCEEDED and carries no envelope, because buying credits is not the same decision as changing plan.


Limits — every one, and what enforces it

Twelve keys. -1 is unlimited, 0 is none.

LimitCountsEnforced by
maxStorageGbNothing. No upload path reads it.
maxContactsNothing. Contact creation is capped by maxUsersPerOrg.

Two counting rules worth knowing: a cancelled or expired contract releases its slot, and a completed project releases its slot. The cap is on what you have, not what you have ever had.


Plan tiers

The summary below is the same data.

MSP / IT partner plans

FreeStarterGrowthScaleEnterprise
Base / month (CAD)$0$109$199$339Custom
Per tech / month$39$55$69Custom
Max seats11025UnlimitedUnlimited
Client organizations21550UnlimitedUnlimited
Users per org5152550Unlimited
Org nesting depth00125
AI credits / month501,0005,00015,000Unlimited
Assets501,0005,00025,000Unlimited
Contracts010UnlimitedUnlimitedUnlimited
Active projects0515UnlimitedUnlimited
Sending domains12510Unlimited
Custom domains0005Unlimited
Change managementViewBasicCABAdvanced
SLA managementBasicAdvancedAdvancedAdvanced
Project management
Email integrationM365+ IMAP+ IMAP
BrandingLogo onlyFull
White-label
SSO (Entra ID / Google)
API accessReadFullFull
Roll-up reportingBasicFull
Catalog controlLimitedFullFullFull
Commission accrual

End-customer plans

FreeEssentialsProfessionalBusinessEnterprise
Base / month (CAD)$0$55$109$199Custom
Per agent / month$25$39$55Custom
Max seats1525UnlimitedUnlimited
Organizations11310Unlimited
Users per org3102550Unlimited
Org nesting depth00013
AI credits / month255002,50010,000Unlimited
Assets255002,50010,000Unlimited
Contracts005UnlimitedUnlimited
Active projects0010UnlimitedUnlimited
Sending domains1135Unlimited
Custom domains0001Unlimited
Change managementViewBasicCABAdvanced
SLA managementBasicAdvancedAdvanced
Project management
Email integrationM365+ IMAP+ IMAP
BrandingLogo onlyFull
White-label
SSO (Entra ID / Google)
API accessReadFullFull
Roll-up reportingBasicFull

Commission accrual is an MSP-plan concept and is off on every customer plan. Catalog control likewise does not appear on customer plans.

Trials. Every paid non-Enterprise tier carries a 14-day trial. Free and Enterprise tiers carry none.


Downgrades, cancellation and the free floor

When a subscription moves to CANCELED, PAST_DUE or SUSPENDED, the account is re-gated to its free floor tiertier_msp_free for MSP and platform tenants, tier_customer_free for everyone else. Both serviceTierId and the cap columns move together, so features and caps can never disagree: a cancellation cannot leave an account reading paid features off a stale tier pointer.

Nothing is deleted. An account that drops from Scale to Free keeps its organizations, assets and contracts; it simply cannot create more until it is back over the cap. Gates block new work only.


Entitlement caps on the account record

Three limits are also stored as columns on Account: maxOrganizations, maxUsersPerOrg, maxNestedDepth. They are checked on hot paths (every user create, every SSO sign-in that auto-provisions) where a join to the tier row would be wasteful.

applyTierToAccount in @solidlio/database is the only place those columns are written, and it is called from every path that changes a plan: signup, subscribe, upgrade, cancel and MSP client provisioning. Rules it applies:

  • Caps come from the tier’s limits, falling back to the schema defaults (1 organization, 5 users, 0 nesting) when a key is missing or malformed — deliberately not unlimited, so malformed tier data can never grant more than it should.
  • On a per-seat tier, the purchased seat count overrides the tier’s maxUsersPerOrg ceiling — except on a tier whose ceiling is already unlimited, which stays unlimited.
  • On a re-gate status the floor tier’s caps are applied instead.

It exists for rows that drifted before applyTierToAccount existed.


Troubleshooting

“This plan requires a custom quote — contact sales” You tried to upgrade to an Enterprise tier. Those have no self-serve price and must be quoted. The API rejects them before any proration is computed.

“This plan requires sales-assisted setup; please contact sales to subscribe” The same rule on the subscribe path (CUSTOM_TIER_CONTACT_SALES).

“This account can only subscribe to end customer tiers” TIER_GROUP_MISMATCH — an end-customer account cannot buy an MSP plan or the reverse. Which group applies is derived from the account’s platform type.

“seats must be between 1 and 10” SEATS_OUT_OF_RANGE. The requested seat count is outside the tier’s minSeats/maxSeats. Move up a tier for a higher seat ceiling.

“Selected plan is not an upgrade” The target is the same rank or lower, or is in a different tier group. Downgrades go through support.

“This account has reached its organization limit for the current plan” PLAN_LIMIT on maxOrganizations. Deactivate an organization or upgrade.

“This organization has reached its user seat limit for the current plan” SEAT_LIMIT on maxUsersPerOrg. Note this is per organization, not per account. On a per-seat tier it reflects the seats you actually purchased, not the tier’s ceiling — buying more seats raises it without changing plan.

“Your plan allows a maximum of 50 asset(s). Upgrade to add more.” PLAN_LIMIT on maxAssets. A bulk import is checked against the whole batch, so a 10,000-row CSV is refused up front rather than half-applied.

“Your plan does not include contracts. Upgrade to add contracts.” maxContracts is 0 on the Free and Essentials tiers. Cancelled and expired contracts do not consume quota.

“Custom domains are not included in your current plan. Upgrade to add one.” maxCustomDomains is 0 below Scale (MSP) and Business (customer).

“Change management tier “BASIC” required. Current tier: “VIEW_ONLY”.” The plan can read changes but not create them. BASIC is the first rung that allows writes.

“Project management isn’t included in your current plan.” projectManagement is off. It is on from Starter (MSP) and Professional (customer).

“Payment not completed (status: requires_payment_method)” Upgrade confirmation ran before the payment succeeded. Complete the payment and retry; the order is reused rather than duplicated.

A platform admin edited a tier and nothing changed for existing accounts. Feature flags are read live, so those take effect at once. The three denormalised caps (maxOrganizations, maxUsersPerOrg, maxNestedDepth) are copied onto the account when its plan changes; run db:backfill:entitlement-caps to push a tier’s edited caps onto accounts that are already on it.


Limits and known behaviour

  • prioritySupport, maxStorageGb and maxContacts are not enforced by any code. They are catalogue metadata. Storage enforcement needs usage accounting that does not exist yet; contacts are governed by the seat cap.
  • An account whose plan grants no apiAccess at all cannot use a key.
  • o365_calendar is never checked in the integrations allowlist. Every tier that grants it also grants o365_email, which is checked on the single Microsoft 365 connect, so the outcome is the same today.
  • The Sol assistant path meters credits without consulting the allowlist.
  • Downgrades are not self-serve by design, so an account cannot drop below what it has already consumed without a conversation.
  • The public pricing page’s comparison grid is static copy. Prices, seats and limits come from the live catalogue, but the long feature-comparison table underneath is maintained by hand and can drift from a tier edit. There is a development-mode warning; there is no production signal.
  • Test gating against a real tenant.
  • db:seed:reference overwrites tier features and limits. Deliberate — it is how the catalogue ships — but it means runtime tier tuning is not durable across a reference re-seed.

Questions this guide did not answer?

Ask us. You will get a reply from someone who uses the product every day.

Book a demo Contact us

A 30-minute walkthrough against your own workflow. No slides.