{"record":{"id":"0bc51bb7af5265bb","repo":"semaphoreui/semaphore","slug":"not-found","errorCode":null,"errorMessage":"Not Found","messagePattern":"Not Found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"api/router.go","lineNumber":612,"sourceCode":"\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc servePublic(w http.ResponseWriter, r *http.Request) {\n\twebPath := \"/\"\n\tif util.WebHostURL != nil {\n\t\twebPath = util.WebHostURL.Path\n\t\tif !strings.HasSuffix(webPath, \"/\") {\n\t\t\twebPath += \"/\"\n\t\t}\n\t}\n\n\treqPath := r.URL.Path\n\tapiPath := path.Join(webPath, \"api\")\n\n\tif reqPath == apiPath || strings.HasPrefix(reqPath, apiPath) {\n\t\thttp.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)\n\t\treturn\n\t}\n\n\t// Check if this is a request for the swagger UI\n\tswaggerPath := path.Join(webPath, \"swagger\")\n\tif reqPath == swaggerPath || reqPath == swaggerPath+\"/\" {\n\t\tserveFile(w, r, \"swagger/index.html\")\n\t\treturn\n\t}\n\n\tif !strings.Contains(reqPath, \".\") {\n\t\tserveFile(w, r, \"index.html\")\n\t\treturn\n\t}\n\n\tnewPath := strings.Replace(\n\t\treqPath,\n\t\twebPath,","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/router.go#L594-L630","documentation":"servePublic (api/router.go:612) is the SPA static-file handler: any request whose path equals or falls under <web_path>/api is rejected with 404 'Not Found'. This stops the public-asset fallback from swallowing API routes — if you see it, the API route you intended was not registered or the request bypassed the API router and was served by the static handler.","triggerScenarios":"Request to a path like /api/... (or /api-something after web_path prefix) that matched the public file server instead of the API router — e.g. wrong HTTP method on a registered route, misspelled API path, or the API subrouter not mounting that endpoint, letting the catch-all servePublic handle it.","commonSituations":"Typos in API URLs (/api/v2alpha vs /api/v2alpha/); calling a newer endpoint against an older Semaphore version that lacks the route; method mismatch (GET vs POST) on a route registered for another method; reverse proxy stripping or mangling the path prefix.","solutions":["Verify the endpoint path and HTTP method against the API docs / registered routes for your Semaphore version.","Upgrade Semaphore if the endpoint was added in a newer release.","Check reverse-proxy rules preserve the /api prefix and forward to the API router.","Confirm the request is not being redirected (trailing-slash or proxy rewrite) into the public asset handler."],"exampleFix":"// before\nfetch(\"/api/my-new-endpoint\")   // 404 via servePublic\n// after: use an existing route/version\nfetch(\"/api/ping\")  // or upgrade server to a version with the route","handlingStrategy":"validation","validationCode":"// guard API calls client-side\nconst API_BASE = \"/api\";\nfunction apiPath(p) { return API_BASE + (p.startsWith(\"/\") ? p : \"/\" + p); }\n// use apiPath(\"/ping\") so typos are caught by shared constants + route whitelist","typeGuard":"function isKnownApiRoute(p, knownRoutes) {\n  return knownRoutes.some(r => r.method === p.method && new RegExp(r.pattern).test(p.path));\n}","tryCatchPattern":"try {\n  const res = await fetch(apiPath(endpoint));\n  if (res.status === 404) throw new Error(\"Endpoint missing on this server version\");\n} catch (e) { /* fall back to supported endpoint or prompt upgrade */ }","preventionTips":["Keep API path constants in one shared module to avoid typos","Check server version compatibility before calling newer endpoints","Watch for reverse proxies rewriting/stripping the /api prefix","Prefer checking /api/config or version endpoint before calling version-gated APIs"],"tags":["routing","http-404","spa"],"backgroundTag":"resource-not-found","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}