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
- Retry key creation without scopes to decouple key issuance from Company Monitoring provisioning
- Inspect the companyMonitoringAccounts row (lifecycle, terminalReason, ownerUserId) to see why no active account exists
- 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.dbView on GitHub (pinned to eeab0a219f)