koala73/worldmonitor · error · ConvexError
INVALID_PREFIX
INVALID_PREFIX
Error message
INVALID_PREFIX
What it means
Input validation on the API-key create mutation: args.keyPrefix does not match the required shape ^wm_[a-f0-9]{5}$ — a 'wm_' prefix followed by exactly five lowercase hex characters. The prefix is generated client-side from the raw key and stored for display, so a malformed prefix means the client generated the key incorrectly or the value was tampered with.
Solutions
- Regenerate the API key client-side using the correct 'wm_' + 5 lowercase hex chars prefix format
- Validate the prefix against ^wm_[a-f0-9]{5}$ before calling the create mutation
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/apiKeys.ts:81 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/051adc9e963ac266.
Report an issue: GitHub.
Appendix: 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 eeab0a219f)