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
- Generate the prefix from the plaintext key as exactly `wm_` + the first 5 lowercase hex characters of the random token portion.
- Ensure the client key-generation code produces lowercase hex only.
- 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
- Derive the prefix from the plaintext key with a single tested helper.
- Unit-test the prefix generator against the regex.
- Never hand-construct the prefix string for production keys.
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
- INVALID_NAME
- INVALID_HASH
- INVALID_API_KEY_SCOPES
- [initRamp] rampCurve entries must be positive integers
- get_intel_timeline requires at least one of domain ("conflic
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/051adc9e963ac266.
Report an issue: GitHub.