Architecture
Billing & plates
OpsDash gates access with a plate ladder, not a flat tier list. The rungs are free → studio → sales → growth → full_loop → agency (defined in packages/config/src/plates.ts). Three flat-rate add-on packs stack on top — AI Pack ($25), Advanced Analytics ($19), and e-Invoicing ($29). Billing is account-level (anchored to a personal workspace), limits are enforced server-side, and Stripe is the single billing source of truth.
The Billing surface itself is not plate-gated — Stripe is the entitlement source, so every plate can open Settings → Billing. The sales rung is dormant (folded into Growth by strict nesting), so it is in the ladder but never sold as its own public column.
Plate overview
| Plate | Target | Monthly | Yearly |
|---|---|---|---|
| Free | Try the loop — contacts, a few projects and forms | $0 | $0 |
| Studio | Bill it — proposals, invoices, recurring & a portal | $24 | $240 |
| Growth | Win work and deliver it — pipeline, projects, contracts | $59 | $590 |
| Full Loop | The whole loop — lead to profit, one database | $99 | $990 |
| Agency | Scale it — SSO, white-label, e-invoicing, priority support | $179 — “Contact us” | $1,790 |
A no-card 7-day plate trial is offered from the first-run plate-decision popup; at trial end with no payment method Stripe cancels the subscription and the org lapses to Free — no surprise charge. All modules are always visible in the sidebar on every plate; sub-features are gated with progressive disclosure — upgrade prompts appear contextually, not as locked nav items.
Workspace limits
Columns are the four public self-serve plates. The dormant sales rung and the “Contact us” agency rung are omitted — see packages/config/src/plates.ts for their full caps.
| Resource | Free | Studio | Growth | Full Loop |
|---|---|---|---|---|
| Workspaces | 1 | 1 | 3 | 5 |
| Included seats | 2 | 2 | 5 | 8 |
| Storage | 500 MB | 5 GB | 25 GB | 100 GB |
| API calls / month | 1,000 | 5,000 | 50,000 | 250,000 |
Sub-feature gating by module
CRM
| Feature key | Free | Studio | Growth | Full Loop |
|---|---|---|---|---|
| crm:contacts | 250 | 500 | 15,000 | 25,000 |
| crm:companies | 50 | 100 | 3,000 | 5,000 |
| crm:deals | — | ✓ | ✓ | ✓ |
| crm:leads | — | ✓ | ✓ | ✓ |
| crm:lead-scoring | — | ✓ | ✓ | ✓ |
| crm:deal-stages | — | ✓ | ✓ | ✓ |
| crm:quotes | — | ✓ | ✓ | ✓ |
| crm:csv-import | — | ✓ | ✓ | ✓ |
| crm:contact-filters | — | ✓ | ✓ | ✓ |
| crm:lead-filters | — | ✓ | ✓ | ✓ |
| crm:email-integration | — | ✓ | ✓ | ✓ |
| crm:export | — | ✓ | ✓ | ✓ |
Projects
| Feature key | Free | Studio | Growth | Full Loop |
|---|---|---|---|---|
| projects:crud | 3 | 3 | 100 | 500 |
| projects:tasks | 100 | 100 | 10,000 | 50,000 |
| projects:kanban | — | — | ✓ | ✓ |
| projects:calendar | — | — | ✓ | ✓ |
| projects:subtasks | — | — | ✓ | ✓ |
| projects:milestones | — | — | ✓ | ✓ |
| projects:gantt | — | — | ✓ | ✓ |
| projects:time-tracking-log | — | — | ✓ | ✓ |
| projects:budget | — | — | ✓ | ✓ |
Platform & module surfaces
| Feature key | Free | Studio | Growth | Full Loop |
|---|---|---|---|---|
| platform:audit-logs | — | — | — | ✓ |
| platform:api-access | — | — | — | ✓ |
| platform:webhooks | — | — | — | ✓ |
| reports:page | — | — | — | ✓ |
| invoicing:page | — | ✓ | ✓ | ✓ |
| ap:page | — | — | — | ✓ |
platform:custom-branding, platform:sso-saml, platform:scim-provisioning, platform:audit-logs-export, and custom roles sit on the Agency rung (not shown as a column above).
Stripe integration
Stripe is the single billing source of truth. Custom logic outside webhook handlers is prohibited.
- Webhook:
POST /api/webhooks/stripehandles all subscription lifecycle events (Connect events land onPOST /api/webhooks/stripe/connect) - Entitlement: an org’s plate is resolved solely from
subscriptions.plan(resolveOrgPlateinpackages/config/src/plates.ts); a feature is enabled when the plate’s rung is at or above the feature’sminPlateinpackages/config/src/defaults.ts - Price IDs: follow
STRIPE_PRICE_{PLATE}_{INTERVAL}(e.g.STRIPE_PRICE_STUDIO_MONTHLY) - Billing portal: Stripe Customer Portal for subscription management (no custom UI for plan changes)
Distribution modes
- saas → billing provider
stripe; the full plate ladder + Checkout/Portal is live - selfHosted / whitelabel → billing provider
noneby default; gating is driven by the signed license’splanCeiling(a plate id) instead of Stripe, and per-request subscription reads short-circuit
See Configuration for Stripe env vars and setup steps.