How to Return 400 Instead of 500 With REST API HTTP Status Codes

A Flask handler that reads data["email"] directly raises a KeyError on a request with no email, which Flask turns into a 500 Internal Server Error - the client gets a scary crash for its own mistake. The fix is to validate before you use the field: if email is missing, return 400 with {"error": "email is required"}. A missing field is a client error (4xx), never a server fault (5xx).

Fullstack Engineerflaskpythonreact

Why a missing field should be a 400, not a 500

REST API HTTP status codes split cleanly by their first digit. 4xx means the client sent something wrong; 5xx means the server broke. A request that omits a required field is squarely a client error - it belongs in the 4xx range, specifically 400 Bad Request.

The bug is that unvalidated input turns a client mistake into a server crash. This Flask handler reads the field directly:

@app.post("/api/users")
def create_user():
    data = request.get_json(silent=True) or {}
    # BUG: data["email"] raises KeyError when the field is missing,
    # which Flask turns into a 500 Internal Server Error.
    email = data["email"]
    user_id = next(_next_id)
    USERS[user_id] = {"id": user_id, "email": email}
    return jsonify(USERS[user_id]), 201

Send a body with no email and data["email"] throws KeyError. Flask catches the unhandled exception and returns 500. The client learns nothing about what it did wrong, dev environments leak a stack trace, and in production a 500 pages your on-call for what is actually a routine bad request.

Validate before you use the field

Check for the field first and return 400 with a message before touching it. This is the fix from the project:

@app.post("/api/users")
def create_user():
    data = request.get_json(silent=True) or {}
    if not data.get("email"):
        return jsonify(error="email is required"), 400
    email = data["email"]
    user_id = next(_next_id)
    USERS[user_id] = {"id": user_id, "email": email}
    return jsonify(USERS[user_id]), 201

Two details matter. data.get("email") returns None instead of raising when the key is absent, so the check itself never crashes. And the tuple (..., 400) sets the status code - without that second value Flask would default to 200 OK, which is just as wrong in the opposite direction.

The valid path is unchanged: a request with an email still creates the user and returns 201 Created.

Verify both paths with curl

Test the client-error path and the success path explicitly, printing the status code:

pkill -f "python3 app.py"; python3 app.py > /tmp/flask.log 2>&1 &

# Missing email -> 400 (was 500)
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/api/users \
  -H 'Content-Type: application/json' -d '{}'

# Valid request -> 201
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/api/users \
  -H 'Content-Type: application/json' -d '{"email":"a@b.com"}'

The first call should print 400, the second 201. On the frontend, a React form reading res.status and body.error now shows a precise message ("400: email is required") instead of a generic failure.

The rule that scales

Validate at the edge, before any handler logic touches the input. Real codebases formalize this with schema validation - Pydantic in FastAPI, Zod in TypeScript, Marshmallow in Flask - which auto-returns 400 or 422 with per-field errors so handlers never see unvalidated data. The principle is the same at any scale: a client's bad input is a 4xx, and only an actual server fault earns a 5xx.

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

FAQ

What HTTP status code should a REST API return for a missing required field?

400 Bad Request is the correct code for a missing or invalid required field. It is a client error - the caller sent something wrong - so it belongs in the 4xx range. Some APIs use 422 Unprocessable Entity for semantic validation errors, but 400 is the standard default.

Why does my Flask API return 500 instead of 400 on bad input?

A 500 means an unhandled exception reached Flask. Reading a missing key with data["email"] raises KeyError, which Flask turns into a 500 Internal Server Error. Validate the field first with data.get("email") and return a 400 explicitly, so the client mistake never becomes a server crash.

What is the difference between a 400 and a 500 status code?

A 400 Bad Request means the client sent something wrong - a missing field, invalid JSON, or a bad value. A 500 Internal Server Error means the server itself failed unexpectedly. The first digit is the signal: 4xx is the caller's fault, 5xx is the server's fault.

How do I return a 400 status code in Flask?

Return a tuple where the second element is the code: return jsonify(error="email is required"), 400. Without the second value Flask defaults to 200, so the status must be explicit in every error branch.

Keep learning

Return the Correct HTTP Status CodeFullstack projectValidate Requests With a Zod Schema in TypeScriptFullstack projectFix a CORS Error in a Web AppFullstack projectFullstack roadmapStep by step to hiredFullstack interview questionsSTAR answersAll Fullstack projectsProjects hub

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 →