windmill-labs/windmill · error · ApiError

ApiError with mapped HTTP status message (e.g. "Not Found"…

Error message

ApiError with mapped HTTP status message (e.g. "Not Found", "Internal Server Error") for non-2xx responses

What it means

This generated OpenAPI client (windmill-client.js) maps known HTTP status codes to their reason phrases ("Not Found", "Internal Server Error", etc., extendable via options.errors) and throws an ApiError carrying the full response whenever the server returns a non-2xx status with a mapped message. It is the client's way of surfacing that the Windmill API rejected the request at the HTTP level.

Solutions

  1. Read error.response.status and .body to identify the actual cause (auth vs not-found vs server error).
  2. For 401/403, refresh the Windmill token (WM_TOKEN) and check the token's workspace/permissions.
  3. For 404, verify the script/job path or id and that the baseUrl points at the correct instance.
  4. For 5xx/429, retry with backoff; check the Windmill server logs for the underlying failure.

Example fix

// before
const job = await client.getJob({ workspace, id });
// after
try {
  const job = await client.getJob({ workspace, id });
} catch (e) {
  if (e instanceof ApiError && e.status === 404) {
    console.error(`Job ${id} not found in workspace ${workspace}`);
  } else {
    throw e;
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (!token) throw new Error('WM_TOKEN missing before calling Windmill API');
if (!workspace) throw new Error('workspace id missing');

Type guard

function isApiError(e) { return e instanceof ApiError && typeof e.status === 'number'; }

Try / catch

try {
  const res = await client.getJob({ workspace, id });
} catch (e) {
  if (isApiError(e)) {
    switch (e.status) {
      case 401: case 403: return refreshTokenAndRetry();
      case 404: return handleNotFound(id);
      default: if (e.status >= 500 || e.status === 429) return retryWithBackoff();
    }
  }
  throw e;
}

Prevention

When it happens

Trigger: Any windmill-client SDK call (e.g. getJob, runScript, listWorkspaces) whose fetch receives a non-2xx response with a status in the errors map: 404 for a missing script/job/workspace, 401/403 for bad token or permissions, 409 conflicts, 429 rate limits, 5xx server errors.

Common situations: Wrong workspace id in the path, an expired or revoked Windmill token, referencing a script path that doesn't exist, requesting a job that was purged, or the server returning 500 during an internal failure.

Understand the failure class

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/950b2ee696e882d3. Report an issue: GitHub.

Appendix: source

Thrown at backend/windmill-runtime-nativets/src/windmill-client.js:3598

    429: "Too Many Requests",
    431: "Request Header Fields Too Large",
    451: "Unavailable For Legal Reasons",
    500: "Internal Server Error",
    501: "Not Implemented",
    502: "Bad Gateway",
    503: "Service Unavailable",
    504: "Gateway Timeout",
    505: "HTTP Version Not Supported",
    506: "Variant Also Negotiates",
    507: "Insufficient Storage",
    508: "Loop Detected",
    510: "Not Extended",
    511: "Network Authentication Required",
    ...options.errors,
  };
  const error = errors[result.status];
  if (error) {
    throw new ApiError(options, result, error);
  }
  if (!result.ok) {
    const errorStatus = result.status ?? "unknown";
    const errorStatusText = result.statusText ?? "unknown";
    const errorBody = (() => {
      try {
        return JSON.stringify(result.body, null, 2);
      } catch (e) {
        return void 0;
      }
    })();
    throw new ApiError(
      options,
      result,
      `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}`
    );
  }
};

View on GitHub (pinned to e474e8803c)