paperclipai/paperclip · error
CreateOS requires an API URL.
Error message
CreateOS requires an API URL.
What it means
parseConfig requires a non-empty apiUrl to construct the CreateOS client; without it no API calls can be made. This error is thrown when the apiUrl key is absent, null, or undefined in the raw config (text() returned null for apiUrl), before any URL validation happens.
Solutions
- Set apiUrl in the plugin config (or the underlying env var the config is sourced from) to the CreateOS base URL, e.g. https://createos.example.com.
- Verify the board/plugin configuration UI actually persisted the apiUrl field and re-save it.
- Check for key renames between plugin versions; migrate old config keys to apiUrl.
- Run onEnvironmentValidateConfig before deploy to catch missing fields early.
Example fix
// before
const config = parseConfig({ apiKey: key }); // apiUrl missing
// after
const config = parseConfig({ apiUrl: process.env.CREATEOS_API_URL, apiKey: key }); Defensive patterns
Strategy: validation
Validate before calling
function requireApiUrl(raw) {
if (raw.apiUrl == null || raw.apiUrl === "")
throw new Error("CreateOS apiUrl is required; set it in plugin config or CREATEOS_API_URL.");
return raw;
} Try / catch
try {
config = parseConfig(raw);
} catch (err) {
if (err.message === "CreateOS requires an API URL.")
throw new Error("CreateOS plugin not configured: set apiUrl (e.g. https://createos.example.com) in the board plugin settings.");
throw err;
} Prevention
- Set CREATEOS_API_URL in every environment (dev, staging, prod) and in secret sync.
- Run onEnvironmentValidateConfig or a config preflight before probing/creating sandboxes.
- Check for config key renames when upgrading the plugin.
- Fail fast at boot with a clear message rather than at first API call.
When it happens
Trigger: Calling createClient/client/parseConfig/onEnvironmentProbe with a config object that omits apiUrl, or where raw.apiUrl is null/undefined (unset env var, missing JSON key, empty secret).
Common situations: Fresh install where CREATEOS_API_URL was never set, config stored per-company but never filled in on the board, renamed config key after an upgrade, or the secrets sync dropped the field.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- ACPX profile requires exact model ; received
- ACPX model must not be empty
- ACPX provider identity contains an invalid permission mode
- ACPX provider identity contains invalid lifetime fences
- ACPX runtime directory must be a directory
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/19cce6bb01c64df2.
Report an issue: GitHub.
Appendix: source
Thrown at packages/plugins/sandbox-providers/createos/src/config.ts:21
apiKey: string | null;
shape: string;
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.");View on GitHub (pinned to 3f1d897a7c)