koala73/worldmonitor · error · ConvexError
PRO_REQUIRED
PRO_REQUIRED
Error message
Notifications are a PRO feature. Upgrade to enable real-time and digest alerts.
What it means
Thrown by assertProEntitled() in the Convex alertRules mutation when the authenticated user's entitlement tier is below 1 (not PRO). The function queries the entitlements table, checks validUntil is in the future, reads the tier from features.tier, and if tier < 1 throws a ConvexError with code 'PRO_REQUIRED'. Notifications (real-time and digest alerts) are gated behind PRO, so any mutation that creates or modifies alert rules must pass this check first.
Source
Thrown at convex/alertRules.ts:49
*
* Kept inline (not imported from entitlements.ts) for security-review
* readability: every alertRules mutation that calls this should be
* trivially auditable in one file.
*/
async function assertProEntitlement(
ctx: MutationCtx,
userId: string,
): Promise<void> {
const entitlement = await ctx.db
.query("entitlements")
.withIndex("by_userId", (q) => q.eq("userId", userId))
.first();
const tier =
entitlement && entitlement.validUntil >= Date.now()
? entitlement.features.tier
: 0;
if (tier < 1) {
throw new ConvexError({
code: "PRO_REQUIRED",
message:
"Notifications are a PRO feature. Upgrade to enable real-time and digest alerts.",
});
}
}
// Cross-field invariant enforcement for (digestMode, sensitivity).
//
// Tightened rule (2026-04-27): real-time delivery is now reserved for
// `critical`-tier events only. `(realtime, all)` and `(realtime, high)` are
// both forbidden. Anything below `critical` lives in a digest cadence
// (daily / twice_daily / weekly).
//
// Why tighter: even on `(realtime, high)`, `high`-severity events fire
// frequently enough on busy days to overload an inbox (severe weather,
// market moves, geopolitics). Real-time is for "interrupt me NOW" content
// only — i.e. genuinely critical. High events still reach the user, justView on GitHub (pinned to ffec79ac33)
Solutions
- Upgrade to PRO — the error message directs the user to upgrade; once the entitlements table reflects tier >= 1 with a valid validUntil, the mutation succeeds.
- If the user IS a paying PRO subscriber, check that their entitlements record exists in Convex and validUntil is in the future — a billing-sync failure may have left the record stale or missing.
- If validUntil recently expired, verify the subscription renewal processed and the entitlements webhook/sync ran.
- For testing, seed the entitlements table with a valid tier-1 record for the test user.
Defensive patterns
Strategy: validation
Validate before calling
// Check entitlement before calling the alert-rule mutation
const entitlement = await convex.query(api.entitlements.getUserEntitlement, { userId });
const tier = entitlement && entitlement.validUntil >= Date.now() ? entitlement.features.tier : 0;
if (tier < 1) {
// Show upgrade prompt instead of calling the mutation
showUpgradePrompt('Notifications are a PRO feature');
return;
}
// Safe to proceed with the mutation
await convex.mutation(api.alertRules.createRule, { userId, ...rule }); Type guard
function isProRequiredError(e: unknown): e is { code: string; message: string } & Error {
return e instanceof Error && (e as any).data?.code === 'PRO_REQUIRED';
} Try / catch
try {
await convex.mutation(api.alertRules.createRule, { userId, ...rule });
} catch (e) {
if (isProRequiredError(e)) {
// Show upgrade UI / redirect to billing
redirectToBilling();
} else throw e;
} Prevention
- Query the user's entitlement (tier >= 1, validUntil in the future) BEFORE showing notification-setup UI.
- Gate the notification/settings UI behind the entitlement check so users never reach the mutation unentitled.
- Handle subscription-expiry gracefully by checking validUntil, not just tier.
- Ensure the billing-to-Convex entitlement sync is reliable so PRO subscribers always have valid records.
When it happens
Trigger: A free-tier (tier 0) or unentitled user calling a Convex mutation that invokes assertProEntitled — e.g. creating an alert rule, enabling notifications, or modifying digest settings. The user has either never subscribed to PRO, their subscription expired (validUntil < Date.now()), or their entitlements record is missing entirely (entitlement query returns null, tier defaults to 0).
Common situations: A free-tier user trying to set up alerts after the free preview ended; a lapsed PRO subscription (validUntil expired); a missing entitlements record for a user who should have PRO (sync failure between the billing system and Convex); a new user who has not yet subscribed.
Related errors
- API_ACCESS_REQUIRED
- COMPANY_MONITORING_ACCESS_DENIED
- INCOMPATIBLE_DELIVERY
- COUNTRIES_LIMIT_EXCEEDED
- TICKERS_LIMIT_EXCEEDED
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/976857570efbcf2c.
Report an issue: GitHub.