paperclipai/paperclip · error
Paperclip API origin is unavailable
Error message
Paperclip API origin is unavailable
What it means
#callApi executes a validated 'call_api' runner operation by dispatching an HTTP request to the Paperclip API itself. It resolves the origin from binding.apiUrl, falling back to process.env.PAPERCLIP_API_URL; at paperclip-runner-tool-authority.ts:247 it throws 'Paperclip API origin is unavailable' when both are absent, because the runner cannot know where to send the authenticated API request.
Source
Thrown at server/src/services/native-runtime/paperclip-runner-tool-authority.ts:247
));
return { approval, tasks: tasks.map((row) => row.issue) };
}
case "report_progress": return this.#reportProgress(input);
case "request_human_input": return this.#requestHumanInput(input,
(await captureRunIdentity(this.db, this.binding)).context?.id ?? null);
case "create_task": return this.#createTask(input,
(await captureRunIdentity(this.db, this.binding)).context?.id ?? null);
case "set_dependencies": return this.#setDependencies(input);
default: throw new Error("paperclip_runner_tool_not_bound");
}
}
async #callApi(callId: string, value: unknown): Promise<unknown> {
const bound = await this.#boundContext();
const context = { ...this.binding, issueIdentifier: bound.issue.identifier, workMode: bound.issue.workMode };
const { input, operation } = validateRunnerApiCall(value, context);
const apiUrl = this.binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
if (!apiUrl) throw new Error("Paperclip API origin is unavailable");
const token = createLocalAgentJwt(this.binding.agentId, this.binding.companyId, bound.actor.adapterType, this.binding.runId, bound.run.responsibleUserId);
if (!token) throw new Error("Paperclip run authentication is unavailable");
const execute = async () => {
const current = await this.#boundContext();
if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error("paperclip_runner_tool_not_advertised");
return executeRunnerApi(input, { ...context, workMode: current.issue.workMode }, {
apiUrl, token,
beforeDispatch: async () => {
const fresh = await this.#boundContext();
if (!runnerApiToolsEnabled(this.binding.companyId, this.binding.apiToolsEnabled)) throw new Error("paperclip_runner_tool_not_advertised");
validateRunnerApiCall(input, { ...context, workMode: fresh.issue.workMode });
},
readFile: (file) => this.#readApiFile(file),
saveResponse: async (bytes, contentType) => {
const storage = this.binding.storage ?? getStorageService();
const saved = await storage.putFile({ companyId: this.binding.companyId, namespace: "runner-api", originalFilename: contentType.includes("json") ? "response.json" : "response.bin", contentType, body: bytes });
const asset = await assetService(this.db).create(this.binding.companyId, { ...saved, createdByAgentId: this.binding.agentId });
const activity = await persistActivity(this.db, { companyId: this.binding.companyId, actorType: "agent", actorId: this.binding.agentId, agentId: this.binding.agentId, runId: this.binding.runId, issueId: this.binding.issueId, action: "asset.created", entityType: "asset", entityId: asset.id, details: { source: "runner.call_api", byteSize: saved.byteSize } });View on GitHub (pinned to 01ad858492)
Solutions
- Set PAPERCLIP_API_URL (e.g. PAPERCLIP_API_URL=http://localhost:3100) in the server/worker environment and restart the process.
- Pass apiUrl explicitly when constructing the runner binding so it does not depend on the environment.
- Check deployment config (.env, docker-compose, k8s manifest) to confirm the variable reaches the process that executes the runner.
- If on an upgraded version, verify the current env var name in doc/DEVELOPING.md or server config and migrate any old variable names.
Example fix
// before (no origin configured)
// binding has no apiUrl; process.env.PAPERCLIP_API_URL undefined
// after
PAPERCLIP_API_URL=http://localhost:3100 pnpm dev
// or: const binding = { ..., apiUrl: process.env.PAPERCLIP_API_URL ?? "http://localhost:3100" }; Defensive patterns
Strategy: validation
Validate before calling
const apiUrl = binding.apiUrl ?? process.env.PAPERCLIP_API_URL;
if (!apiUrl) throw new Error("Set PAPERCLIP_API_URL (or pass binding.apiUrl) before enabling the call_api runner tool");
new URL(apiUrl); // also validate format Type guard
function hasApiOrigin(binding: { apiUrl?: string | null }, env: NodeJS.ProcessEnv): binding is { apiUrl: string } & Record<string, unknown> {
return Boolean(binding.apiUrl ?? env.PAPERCLIP_API_URL);
} Try / catch
try {
return await authority.execute({ tool: "call_api", callId, arguments });
} catch (err) {
if (err instanceof Error && err.message === "Paperclip API origin is unavailable") {
return { error: "api_origin_unconfigured", hint: "Set PAPERCLIP_API_URL in the server environment." };
}
throw err;
} Prevention
- Set PAPERCLIP_API_URL in every environment (dev .env, compose, k8s) that runs the runner.
- Pass apiUrl explicitly in the runner binding for worker processes that may not inherit server env.
- Add a startup check that fails fast when call_api is enabled but no API origin is resolvable.
- Include the env var name and expected format (origin URL) in deployment docs and health checks.
When it happens
Trigger: The runner binding was created without apiUrl and the server process lacks the PAPERCLIP_API_URL environment variable, and the agent invokes the call_api tool (any runner API operation that routes through #callApi).
Common situations: PAPERCLIP_API_URL not set in the deployment environment (missing .env, compose file, or systemd unit); self-hosted install where binding construction does not pass apiUrl; workers spawned in a different process/context that does not inherit the env var; rename of the env var in a version upgrade.
Understand the failure class
Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.
Related errors
- OpenCode evals require exact version 1.18.17; received ${ver
- devUiUrl must use http or https protocol
- devUiUrl must target localhost
- Plugin tool dispatch is not enabled
- No Tailscale address was detected during setup. The saved co
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/cd4deff1c1929755.
Report an issue: GitHub.