semaphoreui/semaphore · warning
Not Found
Error message
Not Found
What it means
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.
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.
Example fix
// before
fetch("/api/my-new-endpoint") // 404 via servePublic
// after: use an existing route/version
fetch("/api/ping") // or upgrade server to a version with the route Defensive patterns
Strategy: validation
Validate before calling
// guard API calls client-side
const API_BASE = "/api";
function apiPath(p) { return API_BASE + (p.startsWith("/") ? p : "/" + p); }
// use apiPath("/ping") so typos are caught by shared constants + route whitelist Type guard
function isKnownApiRoute(p, knownRoutes) {
return knownRoutes.some(r => r.method === p.method && new RegExp(r.pattern).test(p.path));
} Try / catch
try {
const res = await fetch(apiPath(endpoint));
if (res.status === 404) throw new Error("Endpoint missing on this server version");
} catch (e) { /* fall back to supported endpoint or prompt upgrade */ } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/0bc51bb7af5265bb.
Report an issue: GitHub.
Appendix: source
Thrown at api/router.go:612
if err != nil {
fmt.Println(err)
}
}
func servePublic(w http.ResponseWriter, r *http.Request) {
webPath := "/"
if util.WebHostURL != nil {
webPath = util.WebHostURL.Path
if !strings.HasSuffix(webPath, "/") {
webPath += "/"
}
}
reqPath := r.URL.Path
apiPath := path.Join(webPath, "api")
if reqPath == apiPath || strings.HasPrefix(reqPath, apiPath) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
// Check if this is a request for the swagger UI
swaggerPath := path.Join(webPath, "swagger")
if reqPath == swaggerPath || reqPath == swaggerPath+"/" {
serveFile(w, r, "swagger/index.html")
return
}
if !strings.Contains(reqPath, ".") {
serveFile(w, r, "index.html")
return
}
newPath := strings.Replace(
reqPath,
webPath,View on GitHub (pinned to 1774ccb71a)