/api/workflow/* returns HTTP 200 for validation failures, not-found, and server errors
Scripting against the workflow endpoints, you can't trust the HTTP status
code: /api/workflow/save and /api/workflow/delete return 200 OK on
failure, with the real outcome buried in the body's ok/error fields. Sibling
routes in the same codebase signal failures with proper 4xx, so a client can't
pick one convention.
Observed (station-v0.93, routes/dispatch_workflow.py)
Same class of error, different code depending on the handler:
# /api/workflow/stage (_handle_stage, L95) — missing required field → 400 (correct)
return handler._send(400, {"ok": False, "error": "workflow (object) required"})
# /api/workflow/save (_handle_save, L209 / L212) — same class of check → 200
return handler._send(200, {"ok": False, "error": "id + spec required"})
return handler._send(200, {"ok": False, "error": "id must be [A-Za-z0-9_-]{1,80}"})
# /api/workflow/delete (_handle_delete, L307) — resource not found → 200 (should be 404)
return handler._send(200, {"ok": False, "error": "no such composed rail (built-in rails cannot be deleted)"})
# /api/workflow/delete (L316) — server-side os.remove failure → 200 (should be 500)
return handler._send(200, {"ok": False, "error": "delete failed: " + str(e)[:120]})
dispatch_workflow.py uses _send(400 six times but _send(404 zero times
— every not-found in the workflow routes is a 200.
This isn't a house "always-200 with an ok flag" style. routes/schedules.py
gets it right on a comparable surface:
return handler._send(400, {"ok": False, "error": "schedule_id is required"}) # L472
return handler._send(404, {"ok": False, "error": f"unknown schedule {sid!r}"}) # L249, L475
So /api/workflow/* is diverging from the convention its own neighbors follow.
Why it matters
A caller checking the HTTP status — the normal thing to do — sees 200 OK for
a rejected save (bad id, missing spec) and thinks the rail was saved, and
200 OK for a failed delete (rail doesn't exist, or the file delete raised)
and thinks the rail is gone. The failure only shows up if you also parseok/error — but /api/workflow/stage and /api/schedules/* do use status
codes, so there's no single rule that works across the API.
Suggested fix
Bring the workflow handlers in line with schedules.py: 400 for validation
(_handle_save L209/L212), 404 for not-found (_handle_delete L307), 500
for a server-side failure (_handle_delete L316) — keeping the ok/error body
as-is for existing clients.
---
Environment: station-v0.93. Verified by reading routes/dispatch_workflow.py
and routes/schedules.py; status codes quoted from source.