koala73/worldmonitor · error · ConvexError
INVALID_API_KEY_SCOPES
INVALID_API_KEY_SCOPES
Error message
INVALID_API_KEY_SCOPES
What it means
Thrown by normalizeCompanyMonitoringScopes when the scopes array is longer than the total set of valid Company Monitoring scopes, OR contains duplicates (Set size !== array length). The total valid scope set is derived from COMPANY_MONITORING_RPC_SCOPES in shared/company-monitoring-contract. The check rejects oversized or duplicate arrays before even examining individual entries, since either condition proves the array cannot be a subset of valid scopes.
Source
Thrown at convex/apiKeys.ts:20
import { internalMutation, internalQuery, mutation, query } from "./_generated/server";
import { requireUserId, resolveUserId } from "./lib/auth";
import { activeAccountForOwner } from "./companyMonitoring/_shared";
import { ensureActiveAccount } from "./companyMonitoring/accounts";
import {
COMPANY_MONITORING_RPC_SCOPES,
type CompanyMonitoringApiScope,
} from "../shared/company-monitoring-contract";
/** Maximum number of active (non-revoked) API keys per user. */
const MAX_KEYS_PER_USER = 5;
const COMPANY_MONITORING_SCOPES = [
...new Set(Object.values(COMPANY_MONITORING_RPC_SCOPES)),
] as CompanyMonitoringApiScope[];
function normalizeCompanyMonitoringScopes(scopes: string[] | undefined) {
if (!scopes || scopes.length === 0) return undefined;
if (scopes.length > COMPANY_MONITORING_SCOPES.length || new Set(scopes).size !== scopes.length) {
throw new ConvexError("INVALID_API_KEY_SCOPES");
}
if (scopes.some((scope) => !(COMPANY_MONITORING_SCOPES as readonly string[]).includes(scope))) {
throw new ConvexError("INVALID_API_KEY_SCOPES");
}
return [...scopes].sort() as CompanyMonitoringApiScope[];
}
// ---------------------------------------------------------------------------
// Public mutations & queries (require Clerk JWT via ctx.auth)
// ---------------------------------------------------------------------------
/**
* Create a new API key.
*
* The caller must generate the random key client-side (or in the HTTP action)
* and pass the SHA-256 hex hash + the first 8 chars (prefix) here.
* The plaintext key is NEVER stored in Convex.
*View on GitHub (pinned to ffec79ac33)
Solutions
- Dedupe the scopes array client-side before submitting ([...new Set(scopes)]).
- Ensure the array length never exceeds the total valid Company Monitoring scope count.
- If the error persists, log the array to confirm no upstream source is duplicating entries.
Example fix
// before
await createApiKey({ name, keyPrefix, keyHash, scopes: ['read','write','read'] });
// after
const scopes = [...new Set(['read','write'])];
await createApiKey({ name, keyPrefix, keyHash, scopes }); Defensive patterns
Strategy: validation
Validate before calling
import { COMPANY_MONITORING_RPC_SCOPES } from '../shared/company-monitoring-contract';
const VALID = Object.values(COMPANY_MONITORING_RPC_SCOPES);
function normalizeScopes(scopes) {
if (!scopes || scopes.length === 0) return undefined;
const unique = [...new Set(scopes)];
if (unique.length > VALID.length) throw new Error('too many scopes');
if (unique.some(s => !VALID.includes(s))) throw new Error('unknown scope');
return unique.sort();
} Type guard
import { COMPANY_MONITORING_RPC_SCOPES } from '../shared/company-monitoring-contract';
const VALID = new Set(Object.values(COMPANY_MONITORING_RPC_SCOPES));
function isValidScopeList(input): input is string[] {
if (!Array.isArray(input)) return false;
const seen = new Set();
for (const s of input) {
if (typeof s !== 'string' || seen.has(s) || !VALID.has(s)) return false;
seen.add(s);
}
return input.length <= VALID.size;
} Try / catch
try {
await createApiKey(args);
} catch (e) {
if (e instanceof ConvexError && e.message === 'INVALID_API_KEY_SCOPES') {
// dedupe, drop unknown scopes, and retry
} else throw e;
} Prevention
- Dedupe the scopes array ([...new Set(scopes)]) before submitting.
- Source the valid scope list from the same shared contract module the backend uses.
- Add a unit test that asserts every client-offered scope exists in COMPANY_MONITORING_RPC_SCOPES.
When it happens
Trigger: Calling createApiKey with scopes containing duplicate entries (e.g. ['read','read']); calling with a scopes array longer than the total number of valid Company Monitoring RPC scopes.
Common situations: Client concatenating scope lists without dedup; a UI checkbox group allowing duplicate submissions; a misconfigured script passing every scope twice; stale cache returning a duplicated scope list.
Related errors
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/8bb30b2f930581ee.
Report an issue: GitHub.