Pricing & entitlements
Plans¶
- Free (default), Pro, Enterprise (future). Enforcement is on the server via Convex and reflected in the UI.
Entitlements mirror (Convex)¶
- Table:
entitlementswith fields:orgId,planKey,features,updatedAt(indexed byby_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:
experimentsadvanced_analyticswebhookspriority_supportcustom_retention
Clerk features (examples) → Convex keys¶
projects_1→features.projects = 1projects_3→features.projects = 3runs_monthly_limit_10000→features.api_calls_monthly = 10000runs_monthly_limit_250000→features.api_calls_monthly = 250000experiments_enabled→features.experiments = trueadvanced_analytics_enabled→features.advanced_analytics = truewebhooks_enabled→features.webhooks = truepriority_support_enabled→features.priority_support = truedata_retention_days_365→features.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 fromentitlements.features(fallback to Free defaults).checkFeatureAccess(orgId, feature)returns boolean based onentitlements.features[feature].usagemodule 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: