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.

Backend Engineerpythonflaskrest-api

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

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

Repair Broken API PaginationBackend projectAdd CORS Middleware to an Express APIBackend projectAdd a Health Check Endpoint in GoBackend projectBackend roadmapStep by step to hiredBackend interview questionsSTAR answersAll Backend 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 →