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

  1. Dedupe the scopes array client-side before submitting ([...new Set(scopes)]).
  2. Ensure the array length never exceeds the total valid Company Monitoring scope count.
  3. 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

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.