paperclipai/paperclip · error · ToolGatewayHttpError

railway_api_not_verified

railway_api_not_verified

Error message

Railway API access is not verified. Refresh actions or reconnect this Railway connection.

What it means

Before executing a Railway-namespaced tool (RAILWAY_TOOL_PREFIX, e.g. railway_run-command) whose endpoint looks like Railway, the gateway verifies the connection is a Railway connection whose railwayApiStatus is exactly "available". If the stored config is not a Railway connection or its API status is anything else (unverified, error, etc.), it throws this 422 ToolGatewayHttpError with reason code railway_api_not_verified. This guards against executing Railway GraphQL/SSH calls with unverified credentials.

Solutions

  1. Run the connection's 'refresh actions' / Railway API verification flow so railwayApiStatus becomes "available".
  2. Reconnect the Railway connection with a valid Railway API token, then re-verify.
  3. Inspect connection.config.railwayApiStatus in the DB/UI; if it's 'error', fix the underlying token/permissions before retrying.
  4. Confirm the connection was created as a Railway integration (isRailwayConnection) rather than a plain MCP endpoint with a Railway URL.

Example fix

// before
const conn = { config: { url: "https://railway.example.com", railwayApiStatus: "unverified" } };
await runRailwayTool(conn, "railway_run-command", args); // 422
// after
await refreshConnection(conn.id); // re-verifies Railway API
const verified = await getConnection(conn.id);
if (verified.config.railwayApiStatus === "available") await runRailwayTool(verified, "railway_run-command", args);
Defensive patterns

Strategy: validation

Validate before calling

if (conn.config?.railwayApiStatus !== "available") {
  await refreshActions(conn.id); // re-verify Railway API before invoking railway_* tools
}

Type guard

const isVerifiedRailway = (c) => Boolean(c && c.config?.railwayApiStatus === "available" && typeof c.config?.railwaySsh === "object");

Try / catch

try {
  await gateway.invoke({ method: "tools/call", params: { name: "railway_run-command", arguments: args } });
} catch (e) {
  if (e.reasonCode === "railway_api_not_verified") {
    await reconnectAndVerifyRailway(e.details?.connectionId);
  } else throw e;
}

Prevention

When it happens

Trigger: Invoking a railway_* tool when connection.config.railwayApiStatus !== "available" (e.g. the token was never verified, verification failed, or the connection is a generic MCP endpoint that merely matches isRailwayEndpoint), or the connection lacks isRailwayConnection typing.

Common situations: User added a Railway URL endpoint but never ran the 'refresh actions'/API-verification step; the Railway API token was revoked so re-verification flipped status away from 'available'; connection config was hand-edited and railwayApiStatus is missing.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/tool-gateway.ts:5817

      },
    };
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), ms);
    timer.unref?.();
    try {
      const dispatchRemote = (target: string, init: RequestInit) =>
        options.remoteHttpRequest
          ? options.remoteHttpRequest(target, init)
          : guardedRemoteHttpFetch(target, init, {
              ...remoteHttpFetchOptions(),
              // This call site owns a caller-set budget that can exceed the
              // transport's default response deadline, so hand it down rather than
              // letting the tighter default cut a legitimately slow tool short.
              responseTimeoutMs: ms,
            });
      if (isRailwayEndpoint(connection.config.url) && normalizeRailwayToolName(entry.toolName).startsWith(RAILWAY_TOOL_PREFIX)) {
        if (!isRailwayConnection(connection) || connection.config.railwayApiStatus !== "available") {
          throw new ToolGatewayHttpError(422, "Railway API access is not verified. Refresh actions or reconnect this Railway connection.", "railway_api_not_verified");
        }
        const ssh = asRecord(connection.config.railwaySsh);
        const sshRef = grant.credentialSecretRefs.find((ref) => ref.configPath === RAILWAY_SSH_SECRET_PATH);
        execution.request.endpoint = RAILWAY_API_URL;
        execution.request.protocol = entry.toolName === `${RAILWAY_TOOL_PREFIX}run-command` ? "Railway GraphQL + SSH" : "Railway GraphQL";
        const client = createRailwayClient({
          authorization: credentialHeaders.Authorization ?? "",
          signal: controller.signal,
          request: dispatchRemote,
          runCommand: ssh?.grantId === grant.id && ssh?.enabled === true && sshRef
            ? async (input) => runRailwaySshCommand({
                ...input,
                privateKey: await resolveGrantSecretValue(session, connection, grant, sshRef),
                knownHosts: typeof ssh.knownHosts === "string" ? ssh.knownHosts : "",
              })
            : undefined,
        });
        const data = await client.call(entry.toolName, parameters);

View on GitHub (pinned to 3f1d897a7c)