nexu-io/open-design · warning

Enter a valid http(s) website URL.

Error message

Enter a valid http(s) website URL.

What it means

Thrown by startBrandExtraction when the user supplied a url but normalizeUrl(rawUrl) returned null. normalizeUrl prepends https:// when no scheme is present, parses with new URL, and only accepts http/https protocols. The route handler maps this message to HTTP 400 (bad request).

Solutions

  1. Trim whitespace and ensure the URL has an http or https scheme (or no scheme, which normalizeUrl will prefix).
  2. Reject non-http(s) schemes early on the client.
  3. Validate with new URL(value) and check protocol is http: or https: before submitting.

Example fix

// before
await startBrandExtraction({ url: 'ftp://example.com', /* deps */ }); // throws

// after
await startBrandExtraction({ url: 'https://example.com', /* deps */ });
Defensive patterns

Strategy: validation

Validate before calling

function normalizeUrl(raw: string): string | null {
  const trimmed = (raw ?? '').trim();
  if (!trimmed) return null;
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
  let parsed: URL;
  try { parsed = new URL(withScheme); } catch { return null; }
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return null;
  return parsed.href;
}
const normalized = normalizeUrl(userInput);
if (userInput.trim() && !normalized) {
  throw new Error('Enter a valid http(s) website URL.');
}

Type guard

function isHttpUrl(raw: string): boolean {
  try {
    const u = new URL(/^https?:\/\//i.test(raw) ? raw : `https://${raw}`);
    return u.protocol === 'http:' || u.protocol === 'https:';
  } catch {
    return false;
  }
}

Try / catch

try {
  await startBrandExtraction({ url: userInput, /* deps */ });
} catch (err) {
  if (err instanceof Error && err.message === 'Enter a valid http(s) website URL.') {
    return badRequest('Please enter a valid http or https URL.');
  }
  throw err;
}

Prevention

When it happens

Trigger: The URL is malformed ('htp://example', 'example..com'), uses an unsupported scheme ('ftp://', 'file://', 'mailto:'), contains characters that make new URL throw, or is structurally unparseable.

Common situations: User pastes with a typo in the scheme, trailing whitespace, missing dot, or surrounding quotes; CSV/bulk import containing malformed rows; client not trimming input.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/f9e9b84991fe88d3. Report an issue: GitHub.

Appendix: source

Thrown at apps/daemon/src/brands/index.ts:289

  try {
    return new URL(url).hostname.replace(/^www\./i, '');
  } catch {
    return url;
  }
}

/**
 * Reserve a brand and stand up the agent-driven extraction project. Throws on
 * an invalid URL (the route maps that to a 400). The caller navigates into the
 * returned project and auto-sends the seeded prompt to start the agent.
 */
export async function startBrandExtraction(
  opts: StartBrandExtractionOptions,
): Promise<StartBrandExtractionResult> {
  const designMd = normalizeDesignMdInput(opts.designMd);
  const rawUrl = (opts.url ?? '').trim();
  const url = rawUrl ? normalizeUrl(rawUrl) : designMd ? sourceUrlForDesignMd(designMd, opts.description) : null;
  if (rawUrl && !url) throw new Error('Enter a valid http(s) website URL.');
  if (!url) throw new Error('Enter a valid http(s) website URL or paste a DESIGN.md.');
  const hasWebsiteSource = /^https?:\/\//i.test(url);
  const hasDesignMdSource = Boolean(designMd);

  const {
    brandsRoot,
    projectsRoot,
    skillsRoot,
    db,
    randomId = randomUUID,
    logoFallback = ensureLogoFallback,
    seedFallback = ensureBrandSeed,
    imageryFallback = ensureImageryFallback,
  } = opts;
  const id = newBrandId(url);
  const projectId = brandProjectId(id);
  const conversationId = randomId();
  const host = hostnameOf(url);

View on GitHub (pinned to 5be4028344)