How to Return the Correct HTTP Status Code in a REST API
When a validation branch returns jsonify({...}) with no status argument, Flask defaults to 200 OK - even if the body says 'error'. Every client that relies on the status code to detect failure will mishandle it. The fix is one number: add the status code after the response.
Why the wrong status code is a contract violation
HTTP status codes are not cosmetic - they are the protocol-level signal that
clients, proxies, and retry libraries act on. When your API returns 200 OK
for a validation failure, the client has to parse the response body to detect
the error. Generic HTTP clients, retries, and error-handling middleware won't
do that - they'll treat the request as successful.
The common mistake in Flask
In Flask, jsonify() returns a 200 OK by default. Forgetting the status
argument in an error branch is easy to miss:
# Wrong - both branches return 200
@app.post("/orders")
def create_order():
data = request.get_json()
if not data or "item" not in data:
return jsonify({"error": "missing required fields"}) # defaults to 200!
order = save_order(data)
return jsonify(order), 201
The validation path reaches the client as 200 OK with an error body.
Any code that branches on response.status_code == 200 will treat the
failure as a success.
The fix: pass the status code as the second return value
Flask accepts a (response, status_code) tuple from any route. Add the
code:
@app.post("/orders")
def create_order():
data = request.get_json(silent=True)
if not data:
return jsonify({"error": "Request body must be JSON"}), 400
errors = []
for field in ("customer", "item", "quantity"):
if field not in data:
errors.append(f"{field} is required")
if errors:
return jsonify({"error": "Validation failed", "details": errors}), 400
order = save_order(data)
return jsonify(order), 201
Now POST /orders with a missing field returns 400 Bad Request, and a
successful creation returns 201 Created.
The codes that matter for REST APIs
| Status | Meaning | When to use |
|---|---|---|
200 OK |
Generic success | GET responses, updates |
201 Created |
Resource created | Successful POST that creates |
400 Bad Request |
Client sent bad input | Missing fields, validation failures |
401 Unauthorized |
Not authenticated | Missing or invalid credentials |
403 Forbidden |
Not authorized | Authenticated but no permission |
404 Not Found |
Resource missing | Unknown ID or route |
500 Internal Server Error |
Unhandled server fault | Unexpected exceptions |
Verify with curl
After the fix, test both paths explicitly:
# Should return HTTP/1.1 400 Bad Request
curl -i -X POST http://localhost:8000/orders \
-H "Content-Type: application/json" \
-d '{"item": "widget"}'
# Should return HTTP/1.1 201 Created
curl -i -X POST http://localhost:8000/orders \
-H "Content-Type: application/json" \
-d '{"customer": "alice", "item": "widget", "quantity": 2}'
Test suites that assert on both the status code AND the response shape catch this class of bug instantly - asserting on the body alone lets it slip through.
Want to try it hands-on? HeyDevJob gives you this exact setup in a live cloud workspace in your browser - edit it, run it, and see it work. Free, nothing to install.
Try it in a workspace →What you'll practice
- Spotting missing status codes in Flask route handlers
- Returning 400 for validation failures and 201 for successful resource creation
- Verifying status codes with curl -i and writing tests that assert on both status and body
FAQ
Why does Flask return 200 for an error response?
Flask's jsonify() defaults to status 200 if you don't pass a second return value. Forgetting the status code in an error branch means the route returns 200 OK even when the body contains an error message.
How do I return a 400 status code in Flask?
Return a tuple from the route: return jsonify({"error": "..."}), 400. The second element is the HTTP status code. Flask also accepts make_response() if you need to set headers too.
What HTTP status code should a REST API return for validation errors?
400 Bad Request is the correct code for client-side validation failures - missing required fields, wrong types, or values that fail business rules. 422 Unprocessable Entity is a stricter alternative used by some APIs for semantic errors.
What do 1xx, 2xx, 3xx, 4xx, and 5xx mean?
1xx informational, 2xx success (200 OK, 201 Created), 3xx redirection (301, 304), 4xx client error (400, 401, 403, 404), 5xx server error (500, 502, 503). The first digit signals the category.
What are the correct HTTP status codes for a REST API?
200 for a successful GET, 201 for a created resource, 204 for success with no body, 400 for invalid input, 401/403 for auth, 404 for not found, 409 for conflict, and 500 for an unhandled server error.
Keep learning
Learn it by doing. Open this in a live cloud workspace, make the change yourself, and keep a record of the work you can share.
Open the workspace →