{"record":{"id":"c49da4e0cc38a61d","repo":"koala73/worldmonitor","slug":"company-monitoring-access-denied","errorCode":"COMPANY_MONITORING_ACCESS_DENIED","errorMessage":"COMPANY_MONITORING_ACCESS_DENIED","messagePattern":"COMPANY_MONITORING_ACCESS_DENIED","errorType":"error_code","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/apiKeys.ts","lineNumber":74,"sourceCode":"      .query(\"entitlements\")\n      .withIndex(\"by_userId\", (q) => q.eq(\"userId\", userId))\n      .first();\n    if (\n      !entitlement ||\n      entitlement.validUntil < Date.now() ||\n      !entitlement.features.apiAccess\n    ) {\n      throw new ConvexError(\"API_ACCESS_REQUIRED\");\n    }\n\n    const scopes = normalizeCompanyMonitoringScopes(args.scopes);\n    // Issuing a scoped key is a first-use entry point, so it provisions the\n    // root. Requesting no scopes must stay entirely off Company Monitoring.\n    const companyMonitoringAccount = scopes\n      ? await ensureActiveAccount(ctx, userId, entitlement)\n      : null;\n    if (scopes && !companyMonitoringAccount) {\n      throw new ConvexError(\"COMPANY_MONITORING_ACCESS_DENIED\");\n    }\n\n    if (!args.name.trim()) {\n      throw new ConvexError(\"INVALID_NAME\");\n    }\n    if (!/^wm_[a-f0-9]{5}$/.test(args.keyPrefix)) {\n      throw new ConvexError(\"INVALID_PREFIX\");\n    }\n    if (!/^[a-f0-9]{64}$/.test(args.keyHash)) {\n      throw new ConvexError(\"INVALID_HASH\");\n    }\n\n    // Enforce per-user key limit (count only non-revoked keys).\n    //\n    // API keys intentionally reject at the cap instead of silently rotating a\n    // valid key. If a prior race left too many active rows, converge by\n    // revoking enough oldest overflow rows to make room for this create.\n    const existing = await ctx.db","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/apiKeys.ts#L56-L92","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Verify Company Monitoring is enabled for the user's plan tier in the entitlement catalog before requesting scopes.","If scopes are not needed for this key, call createApiKey without the scopes argument (or with an empty array) to skip Company Monitoring entirely.","Inspect ensureActiveAccount in convex/companyMonitoring/accounts.ts to learn why it returned null for this entitlement, and resolve the upstream blocker.","Contact the operator to confirm the account's Company Monitoring eligibility is active and not suspended."],"exampleFix":"// before\nawait createApiKey(ctx, {\n  name, keyPrefix, keyHash,\n  scopes: [\"company-monitoring:read\"], // triggers ensureActiveAccount\n});\n// after — omit scopes when CM is not provisioned\nawait createApiKey(ctx, { name, keyPrefix, keyHash });","handlingStrategy":"validation","validationCode":"// Before createApiKey, confirm CM eligibility or omit scopes\nconst entitlement = await ctx.db.query(\"entitlements\")\n  .withIndex(\"by_userId\", q => q.eq(\"userId\", userId)).first();\nconst cmEnabled = entitlement?.features?.companyMonitoring === true;\nconst scopes = cmEnabled ? requestedScopes : undefined;\nawait createApiKey(ctx, { name, keyPrefix, keyHash, scopes });","typeGuard":"function hasCompanyMonitoring(ent: unknown): ent is { features: { companyMonitoring: true } } {\n  return !!ent && typeof ent === \"object\"\n    && !!((ent as any).features)?.companyMonitoring;\n}","tryCatchPattern":"try {\n  await createApiKey(ctx, { name, keyPrefix, keyHash, scopes });\n} catch (e) {\n  if (e instanceof ConvexError && e.message === \"COMPANY_MONITORING_ACCESS_DENIED\") {\n    // retry without scopes, or surface CM-not-available to the user\n    await createApiKey(ctx, { name, keyPrefix, keyHash });\n  } else throw e;\n}","preventionTips":["Gate the scopes UI option on a Company Monitoring eligibility check before exposing it.","Default scopes to undefined when CM status is unknown.","Surface CM provisioning errors to the user with a 'contact support' path rather than retrying blindly."],"tags":["convex","api-keys","company-monitoring","authorization","entitlements"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}