{"slug": "i-built-my-first-machine-learning-api-here-s-everything-i-learned", "title": "I Built My First Machine Learning API — Here's Everything I Learned", "summary": "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.", "body_md": "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.\n\n```\n.\n├── API/\n│   └── main.py                                 # FastAPI app serving the model\n├── Data/\n│   └── WA_Fn-UseC_-Telco-Customer-Churn.csv    # raw dataset (7,043 customers)\n├── Models/\n│   ├── ml_pipeline.joblib                      # fitted preprocessing + classifier pipeline\n│   └── target_labels.joblib                    # LabelEncoder for the churn target\n├── Notebooks/\n│   └── customer_churn_prediction.ipynb         # data cleaning, EDA, model training\n├── cpp/                                        # (Python virtual environment)\n├── .env                                        # environment variables (not committed)\n├── .gitignore\n├── requirements.txt\n└── README.md\n```\n\n`main.py`\n\nloads `../models/ml_pipeline.joblib`\n\nand `../models/target_labels.joblib`\n\n,\nso it must be run from inside `API/`\n\nfor those relative paths to resolve.\n\nThe raw data is the [Telco Customer Churn dataset](https://www.kaggle.com/datasets/blastchar/telco-customer-churn)\n7,043 rows and 21 columns covering customer demographics, account…\n\nIf 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.\n\nThink 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.\n\nI 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.\n\nI 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`\n\nfile to get a prediction.\n\nThat's the gap an API fills.\n\nBefore any API talk, the notebook had to do its job. Here's the flow, in plain English:\n\n`gender`\n\n, `tenure`\n\n, `Contract`\n\n, `MonthlyCharges`\n\n, and the target column, `Churn`\n\n(Yes/No).`TotalCharges`\n\nbecomes `totalcharges`\n\n), and a numeric column that was secretly stored as text gets converted properly. A handful of broken rows get dropped.`tenure`\n\nand `MonthlyCharges`\n\n) get scaled, and categorical columns (like `Contract`\n\nor `PaymentMethod`\n\n) get one-hot encoded, because models only understand numbers.`joblib`\n\n, 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`\n\n/`0`\n\n.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.\n\nAn 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.\n\nFor this project, the deal is simple:\n\n\"Send me a customer's details, and I'll tell you whether they're likely to churn.\"\n\nThat's it. No magic. Just a structured request in, a structured response out.\n\nI used [FastAPI](https://fastapi.tiangolo.com/), a Python framework built specifically for creating APIs quickly, with automatic validation and interactive documentation baked in.\n\nHere's the skeleton of what's happening in `main.py`\n\n:\n\n``` python\nimport joblib\nfrom fastapi import FastAPI\nfrom pydantic import BaseModel\nimport pandas as pd\n\n# Load the model and target labels\nlabels = joblib.load('../models/target_labels.joblib')\nmodel = joblib.load('../models/ml_pipeline.joblib')\n\napp = FastAPI(\n    title=\"Customer Churn Prediction API\",\n    description=\"An API for predicting customer churn using a pre-trained machine learning model.\"\n)\n```\n\nTwo things happen right at the top, before the API even starts handling requests:\n\n`ml_pipeline.joblib`\n\n) is loaded into memory. This is the exact same preprocessing + model combo we saved from the notebook.`target_labels.joblib`\n\n) is loaded too, so we can turn a `0`\n\n/`1`\n\nprediction back into a human-readable \"No\"/\"Yes\".This only happens **once**, when the server starts — not on every request. That's important for speed.\n\nThis 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:\n\n```\nclass InputData(BaseModel):\n    gender: str\n    seniorcitizen: int\n    partner: str\n    dependents: str\n    tenure: int\n    phoneservice: str\n    multiplelines: str\n    internetservice: str\n    onlinesecurity: str\n    onlinebackup: str\n    deviceprotection: str\n    techsupport: str\n    streamingtv: str\n    streamingmovies: str\n    contract: str\n    paperlessbilling: str\n    paymentmethod: str\n    monthlycharges: float\n    totalcharges: float\n```\n\nEvery field here mirrors a column the model was trained on. If someone sends `tenure`\n\nas `\"twelve\"`\n\ninstead of `12`\n\n, 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.\n\nAn API exposes \"endpoints\" — specific URLs that do specific things. This project has two.\n\n**A health check**, so you (or a monitoring tool) can confirm the API is alive:\n\n``` python\n@app.get(\"/health\", tags=[\"Health Check\"])\ndef health_check():\n    return {\"status\": \"API is running\"}\n```\n\n**The actual prediction endpoint**:\n\n``` python\n@app.post(\"/predict\", tags=[\"Prediction\"])\ndef predict_churn(payload: InputData):\n    # Convert input data to DataFrame\n    input_df = pd.DataFrame([payload.model_dump()])\n\n    # Make prediction\n    prediction = model.predict(input_df)\n\n    # Decode the prediction\n    decoded_prediction = labels.inverse_transform(prediction)\n\n    return {\"prediction\": decoded_prediction[0]}\n```\n\nWalking through it:\n\n`payload`\n\n.`payload.model_dump()`\n\nturns 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(...)`\n\nruns that row through the `0`\n\nor `1`\n\n), so `labels.inverse_transform(...)`\n\nconverts it back into `\"No\"`\n\nor `\"Yes\"`\n\n.That's the whole loop: **JSON in → DataFrame → pipeline → prediction → JSON out.**\n\nOnce you run:\n\n```\nuvicorn main:app --reload\n```\n\nFastAPI gives you a free interactive docs page at `http://127.0.0.1:8000/docs`\n\n, where you can literally fill in a form and hit \"Execute\" to test `/predict`\n\nwithout 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.\n\nSend it something like:\n\n```\n{\n  \"gender\": \"Female\",\n  \"seniorcitizen\": 0,\n  \"partner\": \"Yes\",\n  \"dependents\": \"No\",\n  \"tenure\": 1,\n  \"phoneservice\": \"No\",\n  \"multiplelines\": \"No phone service\",\n  \"internetservice\": \"DSL\",\n  \"onlinesecurity\": \"No\",\n  \"onlinebackup\": \"Yes\",\n  \"deviceprotection\": \"No\",\n  \"techsupport\": \"No\",\n  \"streamingtv\": \"No\",\n  \"streamingmovies\": \"No\",\n  \"contract\": \"Month-to-month\",\n  \"paperlessbilling\": \"Yes\",\n  \"paymentmethod\": \"Electronic check\",\n  \"monthlycharges\": 29.85,\n  \"totalcharges\": 29.85\n}\n```\n\nAnd you get back:\n\n```\n{\"prediction\": \"No\"}\n```\n\nTraining a model answers the question \"does this work?\" Building an API answers a completely different question: **\"can anyone else use this?\"**\n\nThose 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.\n\nIf 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.\n\n*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.*", "url": "https://wpnews.pro/news/i-built-my-first-machine-learning-api-here-s-everything-i-learned", "canonical_source": "https://dev.to/ericmwaimiri/i-built-my-first-machine-learning-api-heres-everything-i-learned-1f7", "published_at": "2026-08-13 22:49:39+00:00", "updated_at": "2026-08-13 23:16:01.555863+00:00", "lang": "en", "topics": ["machine-learning", "developer-tools"], "entities": ["FastAPI", "Telco Customer Churn", "Safaricom", "Airtel", "Jupyter"], "alternates": {"html": "https://wpnews.pro/news/i-built-my-first-machine-learning-api-here-s-everything-i-learned", "markdown": "https://wpnews.pro/news/i-built-my-first-machine-learning-api-here-s-everything-i-learned.md", "text": "https://wpnews.pro/news/i-built-my-first-machine-learning-api-here-s-everything-i-learned.txt", "jsonld": "https://wpnews.pro/news/i-built-my-first-machine-learning-api-here-s-everything-i-learned.jsonld"}}