PYTHON FOR AI • LESSON 5

Build an API Client

So far, you learned HTTP, Requests, JSON, REST APIs, authentication, and error handling. Now we will combine everything into a reusable Python API client.

CORE IDEA

An API client is Python code that knows how to communicate with an API.

Instead of writing the same request code repeatedly, we create reusable functions or a class that handles authentication, requests, errors, and JSON responses.

01

What Is an API Client?

An API client is code that communicates with an API on behalf of your application.

Without a client, you might repeatedly write code like:

import requests

response = requests.get(
    "https://api.example.com/products"
)

data = response.json()

print(data)

If your application needs many API endpoints, repeating this code becomes messy.

Instead, we create a reusable API client.

Your Application
       ↓
   API Client
       ↓
HTTP Request
       ↓
      API
       ↓
JSON Response
       ↓
   API Client
       ↓
Your Application
02

Why Build an API Client?

A reusable client gives you one place to manage API communication.

  • Authentication
  • Base URL
  • HTTP requests
  • Timeouts
  • Error handling
  • JSON responses
  • Reusable API methods

This becomes especially useful when your application communicates with multiple endpoints.

03

Use a Base URL

Most APIs have a common base URL.

https://api.example.com

Individual endpoints are added to that base URL.

/products
/users
/orders

Instead of repeating the full domain everywhere, store it once.

BASE_URL = "https://api.example.com"
04

Build a Simple API Client

Start with a simple function.

import requests


BASE_URL = "https://api.example.com"


def get_products():

    response = requests.get(
        f"{BASE_URL}/products"
    )

    response.raise_for_status()

    return response.json()


products = get_products()

print(products)

The function hides the HTTP details from the rest of your application.

05

Sending Parameters

APIs often accept query parameters.

For example:

/products?limit=10

With Requests, use the params argument.

import requests


BASE_URL = "https://api.example.com"


def get_products(limit=10):

    params = {
        "limit": limit
    }

    response = requests.get(
        f"{BASE_URL}/products",
        params=params
    )

    response.raise_for_status()

    return response.json()


products = get_products(10)

print(products)

Requests creates the query string for you.

06

Add Authentication

Our client can now include an API key.

import os
import requests

from dotenv import load_dotenv


load_dotenv()


BASE_URL = "https://api.example.com"

API_KEY = os.getenv("API_KEY")


headers = {
    "X-API-Key": API_KEY
}

The key should come from an environment variable, not from hardcoded source code.

07

Create a Reusable Request Method

Instead of repeating authentication and error handling in every API method, create one function responsible for making requests.

import os
import requests

from dotenv import load_dotenv


load_dotenv()


class APIClient:

    def __init__(self, base_url):

        self.base_url = base_url

        self.api_key = os.getenv("API_KEY")

        self.headers = {
            "X-API-Key": self.api_key
        }


    def request(self, method, endpoint, **kwargs):

        url = f"{self.base_url}{endpoint}"

        response = requests.request(
            method,
            url,
            headers=self.headers,
            timeout=10,
            **kwargs
        )

        response.raise_for_status()

        return response.json()

Now authentication and common request behavior are handled in one place.

08

Add a GET Method

We can now create a clean method for retrieving products.

class APIClient:

    def __init__(self, base_url):

        self.base_url = base_url

        self.api_key = os.getenv("API_KEY")

        self.headers = {
            "X-API-Key": self.api_key
        }


    def request(self, method, endpoint, **kwargs):

        url = f"{self.base_url}{endpoint}"

        response = requests.request(
            method,
            url,
            headers=self.headers,
            timeout=10,
            **kwargs
        )

        response.raise_for_status()

        return response.json()


    def get_products(self):

        return self.request(
            "GET",
            "/products"
        )

The application now only needs to call:

client = APIClient(
    "https://api.example.com"
)

products = client.get_products()

print(products)
09

Add a POST Method

APIs also allow applications to create resources.

For example, creating a product might require a POST request.

def create_product(self, product):

    return self.request(
        "POST",
        "/products",
        json=product
    )

Then:

product = {
    "name": "AI Laptop",
    "price": 1200
}


created_product = client.create_product(
    product
)


print(created_product)

Notice that the application does not need to know how the HTTP request is constructed.

10

PUT and DELETE

The same client can support other HTTP methods.

Update a Product

def update_product(self, product_id, product):

    return self.request(
        "PUT",
        f"/products/{product_id}",
        json=product
    )

Delete a Product

def delete_product(self, product_id):

    return self.request(
        "DELETE",
        f"/products/{product_id}"
    )
11

Add Error Handling

A real client must handle network and HTTP errors.

def request(self, method, endpoint, **kwargs):

    url = f"{self.base_url}{endpoint}"

    try:

        response = requests.request(
            method,
            url,
            headers=self.headers,
            timeout=10,
            **kwargs
        )

        response.raise_for_status()

        return response.json()


    except requests.exceptions.Timeout:

        print("The API request timed out.")


    except requests.exceptions.ConnectionError:

        print("Could not connect to the API.")


    except requests.exceptions.HTTPError as error:

        print("API returned an HTTP error:", error)


    except requests.exceptions.JSONDecodeError:

        print("The API returned invalid JSON.")


    return None
12

Complete API Client

Now put everything together.

import os
import requests

from dotenv import load_dotenv


load_dotenv()


class APIClient:

    def __init__(self, base_url):

        self.base_url = base_url

        self.api_key = os.getenv("API_KEY")

        if not self.api_key:
            raise ValueError(
                "API_KEY is not configured"
            )

        self.headers = {
            "X-API-Key": self.api_key
        }


    def request(self, method, endpoint, **kwargs):

        url = f"{self.base_url}{endpoint}"

        try:

            response = requests.request(
                method,
                url,
                headers=self.headers,
                timeout=10,
                **kwargs
            )

            response.raise_for_status()

            return response.json()


        except requests.exceptions.Timeout:

            print("The API request timed out.")


        except requests.exceptions.ConnectionError:

            print(
                "Could not connect to the API."
            )


        except requests.exceptions.HTTPError as error:

            print(
                "API returned an HTTP error:",
                error
            )


        except requests.exceptions.JSONDecodeError:

            print(
                "The API returned invalid JSON."
            )


        return None


    def get_products(self):

        return self.request(
            "GET",
            "/products"
        )


    def get_product(self, product_id):

        return self.request(
            "GET",
            f"/products/{product_id}"
        )


    def create_product(self, product):

        return self.request(
            "POST",
            "/products",
            json=product
        )


    def update_product(self, product_id, product):

        return self.request(
            "PUT",
            f"/products/{product_id}",
            json=product
        )


    def delete_product(self, product_id):

        return self.request(
            "DELETE",
            f"/products/{product_id}"
        )
13

Using the API Client

The application can now create the client and call simple methods.

client = APIClient(
    "https://api.example.com"
)


products = client.get_products()

print(products)

Get one product:

product = client.get_product(10)

print(product)

Create a product:

new_product = {
    "name": "AI Laptop",
    "price": 1200
}


product = client.create_product(
    new_product
)

print(product)
14

Organize the Project

Once the client becomes larger, separate it from the main application.

api-client-project/
│
├── .env
├── .gitignore
├── requirements.txt
│
├── api_client.py
│
└── main.py

The API communication belongs in api_client.py.

The application logic belongs in main.py.

15

How the API Client Works

main.py
   ↓
APIClient
   ↓
Authentication
   ↓
Build URL
   ↓
HTTP Request
   ↓
API Server
   ↓
HTTP Response
   ↓
Check Errors
   ↓
Parse JSON
   ↓
Return Data
   ↓
main.py

The application does not need to worry about every HTTP detail. The client handles that complexity.

16

Why This Matters for AI

This pattern becomes extremely useful when building AI applications.

Later, you may communicate with services for:

  • LLM generation
  • Embeddings
  • Vector databases
  • Document processing
  • Image generation
  • Speech processing
  • External business APIs
Your AI Application
        ↓
   Python Client
        ↓
Authentication
        ↓
   HTTP Request
        ↓
     AI API
        ↓
   JSON Response
        ↓
AI Application

Understanding API clients now will make later LLM and RAG lessons much easier.

17

Mini Project — Product API Client

Build a complete client for a fictional product API.

Step 1 — Environment

API_KEY=your_api_key

Step 2 — Client

from api_client import APIClient


client = APIClient(
    "https://api.example.com"
)


products = client.get_products()


if products is not None:

    for product in products:

        print(product)

Step 3 — Test Another Endpoint

product = client.get_product(1)

print(product)

The goal is not simply to make one API request. The goal is to create reusable code that can communicate with multiple endpoints.

18

Good API Client Practices

  • Keep API credentials outside source code.
  • Use environment variables for secrets.
  • Set request timeouts.
  • Handle HTTP errors.
  • Handle connection failures.
  • Keep API communication in one place.
  • Use reusable methods.
  • Keep application logic separate from API logic.
  • Do not blindly assume every response is valid JSON.
  • Read the API documentation before implementing authentication or endpoints.
19

What You Learned

  • What an API client is
  • Why reusable API clients are useful
  • Using a base URL
  • Creating API client functions
  • Using GET requests
  • Using POST requests
  • Using PUT requests
  • Using DELETE requests
  • Adding authentication
  • Handling API errors
  • Handling network failures
  • Parsing JSON responses
  • Creating a reusable API client class
  • Organizing an API client project
  • Why API clients matter for AI applications
KEY TAKEAWAY

Stop repeating API code. Build reusable clients.

A good API client hides authentication, URLs, HTTP requests, error handling, and response parsing behind simple methods. Your application then focuses on what to do with the data instead of repeatedly managing HTTP details.