paperclipai/paperclip · error

PAPERCLIP_API_URL is required to create a routine webhook

Error message

PAPERCLIP_API_URL is required to create a routine webhook

What it means

routineWebhookUrl builds the public webhook URL for a routine trigger from a public base origin: the configured runtime public origin, falling back to the PAPERCLIP_API_URL env var. If neither is set there is no way to construct an externally reachable URL, so it throws. Webhooks must point at a URL the external scheduler can reach, so a guess is unsafe.

Source

Thrown at server/src/services/routines.ts:108

  "issue.read_marked",
  "issue.read_unmarked",
  "issue.inbox_archived",
  "issue.inbox_unarchived",
  "issue.inbox_touched",
];
const WEEKDAY_INDEX: Record<string, number> = {
  Sun: 0,
  Mon: 1,
  Tue: 2,
  Wed: 3,
  Thu: 4,
  Fri: 5,
  Sat: 6,
};

export function routineWebhookUrl(publicId: string): string {
  const baseUrl = runtimePublicOrigin() ?? process.env.PAPERCLIP_API_URL?.trim();
  if (!baseUrl) throw new Error("PAPERCLIP_API_URL is required to create a routine webhook");
  return `${baseUrl.replace(/\/+$/, "")}/api/routine-triggers/public/${publicId}/fire`;
}

type ExecutionIssueTransientFailureStatus = (typeof EXECUTION_ISSUE_TRANSIENT_FAILURE_STATUSES)[number];

function executionIssueTransientFailureReason(status: ExecutionIssueTransientFailureStatus) {
  return `Execution issue moved to ${status}`;
}

function executionIssueTransientFailureStatusFromPayload(payload: unknown): ExecutionIssueTransientFailureStatus | null {
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
  const transientFailure = (payload as Record<string, unknown>).transientFailure;
  if (!transientFailure || typeof transientFailure !== "object" || Array.isArray(transientFailure)) return null;
  const record = transientFailure as Record<string, unknown>;
  if (record.code !== EXECUTION_ISSUE_TRANSIENT_FAILURE_CODE) return null;
  return EXECUTION_ISSUE_TRANSIENT_FAILURE_STATUSES.find((status) => record.status === status) ?? null;
}

View on GitHub (pinned to 01ad858492)

Solutions

  1. Set PAPERCLIP_API_URL to the externally reachable base URL (e.g. https://api.example.com) and restart
  2. Verify the value has no surrounding whitespace and no trailing path issues; the code trims and strips trailing slashes
  3. If embedding, configure the runtime public origin so the env var is unnecessary
  4. In tests, inject a base URL explicitly instead of relying on ambient env

Example fix

// before: running without env config
pnpm dev  # PAPERCLIP_API_URL unset -> throws on routine webhook creation
// after
export PAPERCLIP_API_URL="https://api.myinstance.dev"
pnpm dev
Defensive patterns

Strategy: try-catch

Validate before calling

const baseUrl = process.env.PAPERCLIP_API_URL?.trim();
if (!baseUrl) throw new Error('PAPERCLIP_API_URL must be set to create routine webhooks');
new URL('/api/routine-triggers/public/x/fire', baseUrl); // also validates format

Try / catch

try {
  const url = routineWebhookUrl(publicId);
} catch (e) {
  if ((e as Error).message.includes('PAPERCLIP_API_URL is required')) {
    // surface a clear config error to the operator, not a 500
  } else throw e;
}

Prevention

When it happens

Trigger: Creating or updating a routine with a webhook trigger while PAPERCLIP_API_URL is unset (or empty/whitespace) and no runtime public origin is configured — typical in fresh dev checkouts, container images without env wiring, or tests.

Common situations: Local dev without the env var set; docker-compose/K8s manifest missing PAPERCLIP_API_URL; env var present but empty string or only whitespace; a hosted deployment where only an internal URL was configured.

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


AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10). Data as JSON: /api/errors/5f89afca77f7779a. Report an issue: GitHub.