koala73/worldmonitor · error · ConvexError
TOO_MANY_ORIGINS
TOO_MANY_ORIGINS
Error message
TOO_MANY_ORIGINS
What it means
After trimming, deduping, and validating origins, normalizeAllowedOrigins rejects any set larger than MAX_ALLOWED_ORIGINS (10). The cap exists because embed keys declare a partner's own sites, not a CDN or wildcard proxy list. ConvexError("TOO_MANY_ORIGINS") is thrown before any database write occurs.
Solutions
- Reduce the list to 10 or fewer distinct origins before calling the mutation.
- Deduplicate client-side with `new Set(origins)` — duplicates are free, only unique origins count.
- Consolidate subdomains onto one registrable domain where your embed usage permits, or split usage across multiple embed keys.
- If the use case genuinely needs more origins, it conflicts with the product cap; revoke unused keys or reconsider the origin strategy rather than retrying.
Example fix
// before
const origins = ["https://a.example", "https://a.example", "https://b.example", /* ...14 more */];
await api.embedKeys.createEmbedKey({ name, keyPrefix, keyHash, allowedOrigins: origins });
// after
const origins = [...new Set(allOrigins)].slice(0, 10);
if (new Set(allOrigins).size > 10) throw new Error("Too many origins: max 10 unique");
await api.embedKeys.createEmbedKey({ name, keyPrefix, keyHash, allowedOrigins: origins }); Defensive patterns
Strategy: validation
Validate before calling
const unique = [...new Set((allowedOrigins ?? []).map(o => o.trim()).filter(Boolean))];
if (unique.length > 10) throw new Error(`Max 10 unique origins, got ${unique.length}`); Try / catch
try {
await api.embedKeys.createEmbedKey({ ...args, allowedOrigins });
} catch (e) {
if (e instanceof ConvexError && e.data === "TOO_MANY_ORIGINS") {
// ask the user to prune the list to 10 distinct origins
} else throw e;
} Prevention
- Deduplicate with Set before sending; duplicates are free, unique origins count
- Cap origin pickers in the UI at 10 entries
- Don't bulk-import preview/staging deploy URLs into embed keys
When it happens
Trigger: Calling createEmbedKey or updateEmbedKey with allowedOrigins containing more than 10 distinct valid origins. Duplicates do not count — the check is on the deduplicated Set size, so 12 entries with 3 duplicates (size 9) pass while 11 unique origins fail.
Common situations: Bulk-importing a large allowlist of staging, preview, and production domains; passing every Vercel/Netlify preview deployment URL; scripting key creation across many customers with a shared oversized template list.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/cdc9d2e063f6c9be.
Report an issue: GitHub.
Appendix: source
Thrown at convex/embedKeys.ts:34
* Validation only — nothing enforces these at request time (see schema.ts).
*/
function normalizeAllowedOrigins(origins: string[] | undefined): string[] | undefined {
if (origins === undefined) return undefined;
const normalized = new Set<string>();
for (const raw of origins) {
const value = raw.trim();
if (!value) continue;
let origin: string;
try {
origin = new URL(value).origin;
} catch {
throw new ConvexError("INVALID_ORIGIN");
}
if (origin !== value) throw new ConvexError("INVALID_ORIGIN");
normalized.add(origin);
}
if (normalized.size === 0) return undefined;
if (normalized.size > MAX_ALLOWED_ORIGINS) throw new ConvexError("TOO_MANY_ORIGINS");
return [...normalized].sort();
}
// ---------------------------------------------------------------------------
// Public mutations & queries (require Clerk JWT via ctx.auth)
// ---------------------------------------------------------------------------
/**
* Create a new partner-embed key.
*
* Same shown-once discipline as `convex/apiKeys.ts`: the caller generates the
* random key client-side and passes the SHA-256 hex hash + the display prefix.
* The plaintext key is NEVER stored in Convex.
*
* The gate is the shared account embed predicate — a verified Clerk PRO role
* or active paid embed entitlement — NOT `apiAccess`. An embed key is
* published in the partner's HTML, so it must be mintable by every paid tier;
* Pro and Pro Business are `apiAccess: false` and `createApiKey` rejects them.View on GitHub (pinned to 7d06c8633d)