Skip to content

Pricing & entitlements

Plans

  • Free (default), Pro, Enterprise (future). Enforcement is on the server via Convex and reflected in the UI.

Entitlements mirror (Convex)

  • Table: entitlements with fields: orgId, planKey, features, updatedAt (indexed by by_org).
  • No forced-false in MVP anymore. If no row exists for an org, Free defaults are applied automatically.

Canonical feature keys (used in Convex)

  • Numeric limits:
  • projects (number)
  • api_calls_monthly (number)
  • rate_limit_per_minute (number)
  • data_retention_days (number)
  • Flags:
  • experiments
  • advanced_analytics
  • webhooks
  • priority_support
  • custom_retention

Clerk features (examples) → Convex keys

  • projects_1features.projects = 1
  • projects_3features.projects = 3
  • runs_monthly_limit_10000features.api_calls_monthly = 10000
  • runs_monthly_limit_250000features.api_calls_monthly = 250000
  • experiments_enabledfeatures.experiments = true
  • advanced_analytics_enabledfeatures.advanced_analytics = true
  • webhooks_enabledfeatures.webhooks = true
  • priority_support_enabledfeatures.priority_support = true
  • data_retention_days_365features.data_retention_days = 365

For MVP without webhooks, Convex will use Free defaults unless an entitlement row is present. Later, add a Clerk webhook to upsert these values automatically.

Free defaults (when no entitlement row exists)

{
  "planKey": "free",
  "features": {
    "projects": 1,
    "api_calls_monthly": 10000,
    "rate_limit_per_minute": 100,
    "data_retention_days": 90,
    "experiments": false,
    "advanced_analytics": false,
    "webhooks": false,
    "priority_support": false,
    "custom_retention": false
  }
}

Enforcement summary

  • checkUsageLimit(orgId, limitType) reads numeric limits from entitlements.features (fallback to Free defaults).
  • checkFeatureAccess(orgId, feature) returns boolean based on entitlements.features[feature].
  • usage module tracks monthly API calls; API routes enforce per-minute and monthly caps and increment usage.

Manual override (no Clerk sync yet)

Use the Convex mutation to upsert entitlements for testing/admin:

await convex.mutation(api.entitlements.upsertEntitlements, {
  orgId,
  planKey: "pro",
  features: { projects: 3, api_calls_monthly: 250000, experiments: true }
})