3 · A JSON prediction API
Turn the model into a service other programs can call: a `POST /predict` route reads a JSON body with `request.get_json()`, assembles a feature vector, runs `model.predict`, and returns `jsonify({...})` — with the right HTTP status code for success vs bad input vs server error.
A prediction API is a `POST /predict` route: read the JSON body with `request.get_json()`, turn the feature dict into an ordered vector, call `model.predict`, and return `jsonify({...})`. The HTTP STATUS CODE is part of the contract — `200` for a good prediction, `400/422` when the CLIENT's input is wrong, `500` when something on the SERVER breaks.
Without this:
Without a JSON endpoint, no other system can request a prediction; and without correct status codes, callers can't tell a successful prediction from a rejected request or a crashed server.
Chapter 1 gave you routes that return strings and dicts. Now we build the real thing: a JSON prediction API that other programs call to get a model's answer.
The route is POST /predict. It's POST (not GET) because the caller is sending data — the feature values — in the request body. In Flask you declare that with @app.route("/predict", methods=["POST"]).
Read the body with request.get_json(). Flask's request object holds the incoming request. request.get_json() parses the JSON body into a Python dict. So a request body of {"tenure": 12, "monthly_charges": 80, ...} becomes a dict you can index.
Assemble the feature vector — order matters. The model was trained on features in a specific order. You must build the vector in that exact same order, or you'll feed "monthly_charges" into the slot the model thinks is "tenure" — a silent, dangerous bug. So you pull each feature by name in the trained order: [payload[name] for name in FEATURES].
Predict, then jsonify. Run model.predict (or predict_proba), package the result into a dict, and return jsonify({"prediction": 1, "probability": 0.83}). jsonify serializes the dict to a JSON response with the correct Content-Type: application/json header — the standard way to return JSON from Flask.
Status codes are the contract. A JSON API isn't just a body — it's a body plus a status code, and callers depend on the code to know what happened:
200 OK— the prediction succeeded; the body is the result.400 Bad Request— the request itself is malformed: not valid JSON, or no body at all. Flask can't even parse it.422 Unprocessable Entity— the JSON parsed fine, but its contents are invalid for your endpoint: a feature is missing, non-numeric, or out of range. The syntax is OK; the semantics are wrong. This is the code you'll return for bad feature values (you build that gate in lesson 5).500 Internal Server Error— something broke on your side (a bug, the model file missing). Never the client's fault.
The distinction that trips people up: 400 = "I can't read your request" vs 422 = "I read it, but the values don't make sense." Returning the right one lets a caller react correctly — fix their JSON on a 400, fix their feature values on a 422, retry later on a 500.
The read-along cell shows the Flask route; the runnable cell below it is the pure prediction logic the route wraps — payload dict → ordered vector → model.predict → result dict — running on a tiny trained model.
A Flask `POST /predict` route: `request.get_json()` to read the body, a `400` for malformed JSON, a `422` for bad feature contents, then `jsonify(...)` with a `200` on success.
Python (in browser)
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
A client POSTs perfectly valid JSON to `/predict`, but one required feature is missing and another is the string `"high"` instead of a number. Which HTTP status code best fits this response?
- A prediction API is a `POST /predict` route: `request.get_json()` reads the body, you build the feature vector in the trained order, run `model.predict`, and return `jsonify({...})`.
- Assemble features in the EXACT order the model was trained on — pulling each by name avoids the silent bug of feeding values into the wrong slots.
- The status code is part of the contract: `200` success, `400` unparseable request, `422` valid JSON with invalid contents, `500` a genuine server-side failure.
This `POST /predict` shape — read JSON, build the vector, predict, return JSON with a status code — is how virtually every production model is consumed by other services.
If you remove it: With no JSON endpoint and no status-code discipline, callers can't integrate the model programmatically or even tell a good prediction from a rejected request.