koala73/worldmonitor · error · ConvexError

COMPANY_MONITORING_ACCESS_DENIED

COMPANY_MONITORING_ACCESS_DENIED

Error message

COMPANY_MONITORING_ACCESS_DENIED

What it means

Thrown when the caller requested Company Monitoring scopes but ensureActiveAccount could not provision or return an active companyMonitoringAccounts row for the user — the account is missing, not in the 'entitled' lifecycle, terminally tombstoned, or the entitlement check failed. Issuing a scoped key is a first-use provisioning entry point, so an active account must exist before a scoped key can bind to it.

Solutions

  1. Retry key creation without scopes to decouple key issuance from Company Monitoring provisioning
  2. Inspect the companyMonitoringAccounts row (lifecycle, terminalReason, ownerUserId) to see why no active account exists
  3. Re-entitle or re-activate the Company Monitoring account, then request the scoped key again
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at convex/apiKeys.ts:74 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of koala73/worldmonitor@eeab0a219f (2026-08-21). Data as JSON: /api/errors/c49da4e0cc38a61d. Report an issue: GitHub.

Appendix: source

Thrown at convex/apiKeys.ts:74

      .query("entitlements")
      .withIndex("by_userId", (q) => q.eq("userId", userId))
      .first();
    if (
      !entitlement ||
      entitlement.validUntil < Date.now() ||
      !entitlement.features.apiAccess
    ) {
      throw new ConvexError("API_ACCESS_REQUIRED");
    }

    const scopes = normalizeCompanyMonitoringScopes(args.scopes);
    // Issuing a scoped key is a first-use entry point, so it provisions the
    // root. Requesting no scopes must stay entirely off Company Monitoring.
    const companyMonitoringAccount = scopes
      ? await ensureActiveAccount(ctx, userId, entitlement)
      : null;
    if (scopes && !companyMonitoringAccount) {
      throw new ConvexError("COMPANY_MONITORING_ACCESS_DENIED");
    }

    if (!args.name.trim()) {
      throw new ConvexError("INVALID_NAME");
    }
    if (!/^wm_[a-f0-9]{5}$/.test(args.keyPrefix)) {
      throw new ConvexError("INVALID_PREFIX");
    }
    if (!/^[a-f0-9]{64}$/.test(args.keyHash)) {
      throw new ConvexError("INVALID_HASH");
    }

    // Enforce per-user key limit (count only non-revoked keys).
    //
    // API keys intentionally reject at the cap instead of silently rotating a
    // valid key. If a prior race left too many active rows, converge by
    // revoking enough oldest overflow rows to make room for this create.
    const existing = await ctx.db

View on GitHub (pinned to eeab0a219f)