koala73/worldmonitor · error · ConvexError

INVALID_PREFIX

INVALID_PREFIX

Error message

INVALID_PREFIX

What it means

Thrown by createApiKey when args.keyPrefix does not match the regex ^wm_[a-f0-9]{5}$. The prefix is the visible portion of the API key shown in the UI (format: wm_ followed by exactly 5 lowercase hex characters) and must be derived from the generated plaintext key. It is a format guard ensuring the prefix matches the canonical key scheme.

Source

Thrown at convex/apiKeys.ts:81

    ) {
      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
      .query("userApiKeys")
      .withIndex("by_userId", (q) => q.eq("userId", userId))
      .collect();
    const active = existing.filter((k) => !k.revokedAt);
    let activeCount = active.length;
    if (active.length > MAX_KEYS_PER_USER) {
      active.sort((a, b) => a.createdAt - b.createdAt);

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Generate the prefix from the plaintext key as exactly `wm_` + the first 5 lowercase hex characters of the random token portion.
  2. Ensure the client key-generation code produces lowercase hex only.
  3. Verify the prefix string passed to createApiKey matches /^wm_[a-f0-9]{5}$/ before sending.

Example fix

// before
await createApiKey(ctx, { name, keyPrefix: "WM_AbC12", keyHash });
// after — derive prefix from the generated key
const token = crypto.getRandomValues(new Uint8Array(16));
const hex = [...token].map(b => b.toString(16).padStart(2, "0")).join("");
const keyPrefix = `wm_${hex.slice(0, 5)}`; // wm_ + 5 lowercase hex
await createApiKey(ctx, { name, keyPrefix, keyHash });
Defensive patterns

Strategy: validation

Validate before calling

function validPrefix(p: string): boolean {
  return /^wm_[a-f0-9]{5}$/.test(p);
}
if (!validPrefix(keyPrefix)) throw new Error("keyPrefix must be wm_ + 5 lowercase hex");
await createApiKey(ctx, { name, keyPrefix, keyHash });

Type guard

function isKeyPrefix(p: unknown): p is string {
  return typeof p === "string" && /^wm_[a-f0-9]{5}$/.test(p);
}

Try / catch

try {
  await createApiKey(ctx, { name, keyPrefix, keyHash });
} catch (e) {
  if (e instanceof ConvexError && e.message === "INVALID_PREFIX") {
    keyPrefix = derivePrefix(plaintextKey); // regenerate correctly
    await createApiKey(ctx, { name, keyPrefix, keyHash });
  } else throw e;
}

Prevention

When it happens

Trigger: Calling createApiKey with a keyPrefix that is missing the wm_ prefix, has uppercase hex, wrong length, non-hex characters, or is derived from a key generator that does not produce the wm_<5-hex> shape.

Common situations: The client-side key generator produced a different prefix format (e.g. used uppercase, omitted the wm_ scheme, or sliced the wrong number of characters); a manually constructed prefix for testing did not follow the format; version mismatch between the key-generation utility and the server expectation.

Related errors


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