paperclipai/paperclip · error

CreateOS API URL is invalid.

Error message

CreateOS API URL is invalid.

What it means

After confirming apiUrl is present, parseConfig parses it with new URL() and rejects strings that are not absolute, well-formed URLs. This error means the configured apiUrl could not be parsed as a URL at all — it is not a syntax/protocol-policy rejection, which produces different messages.

Solutions

  1. Change apiUrl to a full absolute URL including scheme, e.g. https://createos.example.com (http allowed only for localhost/127.0.0.1/[::1]).
  2. Trim surrounding whitespace and remove stray quotes from the configured value.
  3. Validate in the browser/node with `new URL(value)` before saving the config to fail fast with a friendly message.
  4. If a path prefix is needed, keep it to "", "/", "/v1", or "/v1/" — other paths also fail validation.

Example fix

// before
parseConfig({ apiUrl: "createos.internal", apiKey });
// after
parseConfig({ apiUrl: "https://createos.internal", apiKey });
Defensive patterns

Strategy: validation

Validate before calling

function prevalidateUrl(value) {
  let url;
  try { url = new URL(String(value).trim()); } catch { throw new Error(`apiUrl '${value}' is not a valid absolute URL; include the scheme (https://...)`); }
  if (!url.protocol.startsWith("http")) throw new Error("apiUrl must use http(s)");
  return url;
}

Try / catch

try {
  config = parseConfig(raw);
} catch (err) {
  if (err.message === "CreateOS API URL is invalid.")
    throw new Error(`apiUrl '${raw.apiUrl}' is not a valid URL. Use a full absolute URL like https://createos.example.com (path must be '', '/', '/v1', or '/v1/').`);
  throw err;
}

Prevention

When it happens

Trigger: Configuring apiUrl as "createos.example.com" (missing scheme), "https://" (empty host), a value with spaces or invalid characters, or a relative path like "/api".

Common situations: Users omitting the https:// prefix when filling in the board config form, copy-paste introducing whitespace/quotes, or env files where the value got mangled (e.g. unescaped # truncating the URL).

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/e10b1002cf0c4644. Report an issue: GitHub.

Appendix: source

Thrown at packages/plugins/sandbox-providers/createos/src/config.ts:23

  rootfs: string | null;
  region: string | null;
  timeoutMs: number;
  reuseLease: boolean;
}

export function parseConfig(raw: Record<string, unknown>): CreateosConfig {
  const text = (key: string): string | null => {
    const value = raw[key];
    if (value == null) return null;
    if (typeof value !== "string" || !value.trim() || value.includes("\0")) {
      throw new Error(`${key} must be a non-empty string.`);
    }
    return value.trim();
  };
  const apiUrl = text("apiUrl");
  if (!apiUrl) throw new Error("CreateOS requires an API URL.");
  let url: URL;
  try { url = new URL(apiUrl); } catch { throw new Error("CreateOS API URL is invalid."); }
  // Configuration is board-owned, but never follow redirects with the API key.
  // Plain HTTP is useful for a loopback development server only.
  const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
  if ((url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) ||
      url.username || url.password || url.search || url.hash ||
      !["", "/", "/v1", "/v1/"].includes(url.pathname)) {
    throw new Error("CreateOS API URL must be an HTTPS origin (optionally ending in /v1); HTTP is allowed on loopback only.");
  }
  const shape = text("shape");
  if (!shape) throw new Error("CreateOS requires a shape from its shape catalog.");
  const timeoutMs = raw.timeoutMs ?? 300_000;
  if (typeof timeoutMs !== "number" || !Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 86_400_000) {
    throw new Error("timeoutMs must be an integer between 1 and 86400000.");
  }
  if (raw.reuseLease != null && typeof raw.reuseLease !== "boolean") {
    throw new Error("reuseLease must be a boolean.");
  }
  return {

View on GitHub (pinned to 3f1d897a7c)