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, just

View on GitHub (pinned to ffec79ac33)

Solutions

  1. 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.
  2. 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.
  3. If validUntil recently expired, verify the subscription renewal processed and the entitlements webhook/sync ran.
  4. 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

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


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/976857570efbcf2c. Report an issue: GitHub.