koala73/worldmonitor · error · ConvexError

COMPANY_MONITORING_ACCESS_DENIED

COMPANY_MONITORING_ACCESS_DENIED

Error message

COMPANY_MONITORING_ACCESS_DENIED

What it means

Thrown by createApiKey when the caller requests Company Monitoring scopes but ensureActiveAccount() returns null — meaning the root Company Monitoring account could not be provisioned or activated for this user/entitlement. Scopes were requested (non-empty), so the key cannot be issued without a backing account; requesting no scopes skips this entirely. This guards scoped keys against being minted with no usable backing account.

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 ffec79ac33)

Solutions

  1. Verify Company Monitoring is enabled for the user's plan tier in the entitlement catalog before requesting scopes.
  2. If scopes are not needed for this key, call createApiKey without the scopes argument (or with an empty array) to skip Company Monitoring entirely.
  3. Inspect ensureActiveAccount in convex/companyMonitoring/accounts.ts to learn why it returned null for this entitlement, and resolve the upstream blocker.
  4. Contact the operator to confirm the account's Company Monitoring eligibility is active and not suspended.

Example fix

// before
await createApiKey(ctx, {
  name, keyPrefix, keyHash,
  scopes: ["company-monitoring:read"], // triggers ensureActiveAccount
});
// after — omit scopes when CM is not provisioned
await createApiKey(ctx, { name, keyPrefix, keyHash });
Defensive patterns

Strategy: validation

Validate before calling

// Before createApiKey, confirm CM eligibility or omit scopes
const entitlement = await ctx.db.query("entitlements")
  .withIndex("by_userId", q => q.eq("userId", userId)).first();
const cmEnabled = entitlement?.features?.companyMonitoring === true;
const scopes = cmEnabled ? requestedScopes : undefined;
await createApiKey(ctx, { name, keyPrefix, keyHash, scopes });

Type guard

function hasCompanyMonitoring(ent: unknown): ent is { features: { companyMonitoring: true } } {
  return !!ent && typeof ent === "object"
    && !!((ent as any).features)?.companyMonitoring;
}

Try / catch

try {
  await createApiKey(ctx, { name, keyPrefix, keyHash, scopes });
} catch (e) {
  if (e instanceof ConvexError && e.message === "COMPANY_MONITORING_ACCESS_DENIED") {
    // retry without scopes, or surface CM-not-available to the user
    await createApiKey(ctx, { name, keyPrefix, keyHash });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling convex/apiKeys:createApiKey with a non-empty scopes array where ensureActiveAccount(ctx, userId, entitlement) resolves to null — e.g. the entitlement is valid for API access but the Company Monitoring provisioning path (accounts.ts:ensureActiveAccount) declined to create/activate the root account (disabled feature, quota, or internal guard returned null).

Common situations: The user's plan tier allows apiAccess but Company Monitoring is not enabled or has been suspended for the account; an entitlement catalog change removed Company Monitoring eligibility mid-cycle; a stale/inconsistent entitlement row passes the apiAccess gate but fails the downstream provisioning check.

Related errors


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