I Built My First Machine Learning API — Here's Everything I Learned A developer built a customer churn prediction API using the Telco Customer Churn dataset, training three classifiers and serving the best model via FastAPI. The project packages the entire preprocessing and modeling pipeline into reusable joblib artifacts, enabling any application to request churn predictions over the web. Predicts whether a telecom customer will churn using the Telco Customer Churn dataset. The project covers data cleaning and EDA, comparison of three classifiers, and a FastAPI service that serves the final model. . ├── API/ │ └── main.py FastAPI app serving the model ├── Data/ │ └── WA Fn-UseC -Telco-Customer-Churn.csv raw dataset 7,043 customers ├── Models/ │ ├── ml pipeline.joblib fitted preprocessing + classifier pipeline │ └── target labels.joblib LabelEncoder for the churn target ├── Notebooks/ │ └── customer churn prediction.ipynb data cleaning, EDA, model training ├── cpp/ Python virtual environment ├── .env environment variables not committed ├── .gitignore ├── requirements.txt └── README.md main.py loads ../models/ml pipeline.joblib and ../models/target labels.joblib , so it must be run from inside API/ for those relative paths to resolve. The raw data is the Telco Customer Churn dataset https://www.kaggle.com/datasets/blastchar/telco-customer-churn 7,043 rows and 21 columns covering customer demographics, account… If you've ever trained a model in a Jupyter notebook and then wondered "okay... now what?" — this post is for you. I recently built a Customer Churn Prediction API , and I want to walk you through it the way I wish someone had walked me through my first one: no jargon dump, just what each piece does and why it's there. Think of it like a Safaricom customer care team trying to figure out which subscribers are about to switch to Airtel — before they actually leave. That's churn prediction. And instead of leaving that insight trapped inside a notebook, I turned it into an API anyone or any app can call. I started, like most people do, in a Jupyter notebook. I had a dataset of telecom customers — their contract type, monthly charges, whether they had streaming services, and so on — with a label saying whether they churned left or not. I cleaned the data, explored it, trained a few models, and picked a winner. Cool. But here's the thing: a trained model sitting in a notebook is useless to anyone else. A frontend developer can't "import your notebook" into an app. A business analyst can't click a button in your .ipynb file to get a prediction. That's the gap an API fills. Before any API talk, the notebook had to do its job. Here's the flow, in plain English: gender , tenure , Contract , MonthlyCharges , and the target column, Churn Yes/No . TotalCharges becomes totalcharges , and a numeric column that was secretly stored as text gets converted properly. A handful of broken rows get dropped. tenure and MonthlyCharges get scaled, and categorical columns like Contract or PaymentMethod get one-hot encoded, because models only understand numbers. joblib , I saved the entire fitted pipeline preprocessing + model, bundled together plus the label encoder, so predictions later come back as "Yes"/"No" instead of 1 / 0 .That last step is the bridge to everything that follows. Once your pipeline is saved to disk, it stops being "notebook code" and becomes a reusable artifact — a file any Python program can load and use. An API is just a way for two programs to talk to each other over the web, using a set of agreed-upon rules. You send it something, it sends something back. For this project, the deal is simple: "Send me a customer's details, and I'll tell you whether they're likely to churn." That's it. No magic. Just a structured request in, a structured response out. I used FastAPI https://fastapi.tiangolo.com/ , a Python framework built specifically for creating APIs quickly, with automatic validation and interactive documentation baked in. Here's the skeleton of what's happening in main.py : python import joblib from fastapi import FastAPI from pydantic import BaseModel import pandas as pd Load the model and target labels labels = joblib.load '../models/target labels.joblib' model = joblib.load '../models/ml pipeline.joblib' app = FastAPI title="Customer Churn Prediction API", description="An API for predicting customer churn using a pre-trained machine learning model." Two things happen right at the top, before the API even starts handling requests: ml pipeline.joblib is loaded into memory. This is the exact same preprocessing + model combo we saved from the notebook. target labels.joblib is loaded too, so we can turn a 0 / 1 prediction back into a human-readable "No"/"Yes".This only happens once , when the server starts — not on every request. That's important for speed. This is the part that impressed me most as a beginner. FastAPI uses something called Pydantic to define exactly what shape your input data must be: class InputData BaseModel : gender: str seniorcitizen: int partner: str dependents: str tenure: int phoneservice: str multiplelines: str internetservice: str onlinesecurity: str onlinebackup: str deviceprotection: str techsupport: str streamingtv: str streamingmovies: str contract: str paperlessbilling: str paymentmethod: str monthlycharges: float totalcharges: float Every field here mirrors a column the model was trained on. If someone sends tenure as "twelve" instead of 12 , FastAPI rejects the request automatically — before it ever reaches your model. You don't write a single line of manual validation code. That alone saved me from a bunch of silent bugs. An API exposes "endpoints" — specific URLs that do specific things. This project has two. A health check , so you or a monitoring tool can confirm the API is alive: python @app.get "/health", tags= "Health Check" def health check : return {"status": "API is running"} The actual prediction endpoint : python @app.post "/predict", tags= "Prediction" def predict churn payload: InputData : Convert input data to DataFrame input df = pd.DataFrame payload.model dump Make prediction prediction = model.predict input df Decode the prediction decoded prediction = labels.inverse transform prediction return {"prediction": decoded prediction 0 } Walking through it: payload . payload.model dump turns it into a plain dictionary, which gets wrapped into a one-row pandas DataFrame — because that's the format the trained pipeline expects. model.predict ... runs that row through the 0 or 1 , so labels.inverse transform ... converts it back into "No" or "Yes" .That's the whole loop: JSON in → DataFrame → pipeline → prediction → JSON out. Once you run: uvicorn main:app --reload FastAPI gives you a free interactive docs page at http://127.0.0.1:8000/docs , where you can literally fill in a form and hit "Execute" to test /predict without writing a single line of client code. As a beginner, this is where it finally clicked for me — seeing my model respond to a request in real time made it feel like a real product, not just a school notebook exercise. Send it something like: { "gender": "Female", "seniorcitizen": 0, "partner": "Yes", "dependents": "No", "tenure": 1, "phoneservice": "No", "multiplelines": "No phone service", "internetservice": "DSL", "onlinesecurity": "No", "onlinebackup": "Yes", "deviceprotection": "No", "techsupport": "No", "streamingtv": "No", "streamingmovies": "No", "contract": "Month-to-month", "paperlessbilling": "Yes", "paymentmethod": "Electronic check", "monthlycharges": 29.85, "totalcharges": 29.85 } And you get back: {"prediction": "No"} Training a model answers the question "does this work?" Building an API answers a completely different question: "can anyone else use this?" Those are two separate skills, and honestly, the second one felt more like real software engineering — dependency management, input validation, documentation, deployment thinking — than the modeling part did. If you've been putting off learning how to serve your models, don't. It's a smaller leap than it looks, and FastAPI in particular makes it a genuinely beginner-friendly one. If you're working through something similar — maybe predicting M-Pesa transaction fraud, or matatu route demand — the pattern is identical: train, save the pipeline, load it in an API, validate input, predict, respond. Once you've done it once, you'll do it in your sleep. Have you built your first ML API yet? What tripped you up the most — validation, deployment, or something else entirely? Let me know in the comments.