How to use an API in Python

Want to use APIs in Python? This guide shows you different methods, tips, real-world applications, and how to debug common errors.

How to use an API in Python
Published on: 
Fri
Feb 13, 2026
Updated on: 
Tue
Feb 24, 2026
The Replit Team Logo Image
The Replit Team

APIs let your Python applications communicate with other software and access vast amounts of data. A solid grasp of API interaction is essential for any developer who builds connected applications.

Here, you'll learn the core techniques to consume APIs with Python. You'll find practical tips, see real-world applications, and get advice to debug common issues that arise when you work with external data sources.

Basic API request with requests

import requests

response = requests.get("https://api.github.com/users/python")
data = response.json()
print(f"User: {data['login']}, Followers: {data['followers']}")--OUTPUT--User: python, Followers: 10923

The requests library is the standard for making HTTP requests in Python because it handles the complex parts for you. The code sends a GET request to the GitHub API to fetch public data for a specific user.

The most important part is the response.json() method. It's a convenient shortcut that does two things:

  • It checks that the response is valid JSON.
  • It converts the JSON data into a native Python dictionary.

This conversion is why you can immediately access data points like 'login' and 'followers' using standard dictionary keys, making the API data easy to work with in your code.

Common API interaction techniques

That basic GET request is a great starting point, but you'll often need to customize your calls with parameters, headers, and authentication.

Using parameters and headers with requests

import requests

params = {"q": "python", "sort": "stars"}
headers = {"Accept": "application/json", "User-Agent": "PythonTutorial"}
response = requests.get("https://api.github.com/search/repositories",
params=params, headers=headers)
results = response.json()
print(f"Found {results['total_count']} Python repositories")--OUTPUT--Found 4456789 Python repositories

This example builds on the basic request by adding parameters and headers. You pass these as dictionaries to the requests.get() function to customize the API call.

  • The params dictionary is automatically URL-encoded and added to the request. It's how you filter, sort, or paginate API results.
  • The headers dictionary sends metadata with your request. Here, Accept tells the server you want a JSON response, and User-Agent identifies your application, which many APIs require.

Handling JSON responses

import requests
import json

response = requests.get("https://api.github.com/users/python")
pretty_json = json.dumps(response.json(), indent=2)
print("User data preview:")
print(pretty_json[:100] + "...")--OUTPUT--User data preview:
{
"login": "python",
"id": 1525981,
"node_id": "MDEyOk9yZ2FuaXphdGlvbjE1MjU5ODE=",
"avatar_url": "https://...

While response.json() is perfect for processing data, you'll often want to inspect the raw JSON response for debugging. The built-in json library helps you format this data for readability.

  • The json.dumps() function takes your Python dictionary and converts it back into a JSON-formatted string.
  • Passing indent=2 as an argument adds whitespace to the string, making the nested structure easy to scan. This is especially useful when dealing with large or complex API responses.

Working with authentication

import requests
from requests.auth import HTTPBasicAuth

url = "https://api.example.com/protected-resource"
response = requests.get(
url,
auth=HTTPBasicAuth('username', 'password')
# Alternative: headers={"Authorization": "Bearer your_token_here"}
)
print(f"Status: {response.status_code}, Authenticated: {response.ok}")--OUTPUT--Status: 200, Authenticated: True

Many APIs protect their data, so you'll need to authenticate your requests. The requests library simplifies this with the auth parameter. For APIs that use a username and password, you can pass an HTTPBasicAuth object directly, and the library handles the encoding for you.

  • Another common method is token-based authentication. You'd send a special key, often called a bearer token, inside the Authorization header.
  • After the request, you can check the response.ok attribute. It's a simple boolean that tells you if the API call succeeded without you having to check the specific status_code.

Advanced API techniques

Beyond basic requests, you'll need to manage asynchronous operations with libraries like aiohttp, build wrapper classes, and handle practical limits to create scalable applications.

Asynchronous API requests with aiohttp

import asyncio
import aiohttp

async def fetch_data(session, url):
async with session.get(url) as response:
return await response.json()

async def main():
urls = ["https://api.github.com/users/python", "https://api.github.com/users/django"]
async with aiohttp.ClientSession() as session:
results = await asyncio.gather(*[fetch_data(session, url) for url in urls])
for i, result in enumerate(results):
print(f"API {i+1}: {result['login']} has {result['followers']} followers")

asyncio.run(main())--OUTPUT--API 1: python has 10923 followers
API 2: django has 8754 followers

When you need to make many API calls, running them sequentially with requests can be slow. The aiohttp library solves this by handling requests concurrently. Your application can start multiple requests without waiting for each one to finish, which dramatically speeds up data-heavy tasks.

  • The async and await keywords are central. They signal that an operation can be paused while waiting for a network response, freeing your program to work on other tasks.
  • A single aiohttp.ClientSession manages all connections, which is more efficient than creating a new one for every call.
  • The asyncio.gather function is what executes all your API calls at once, collecting the results after they all complete.

Creating API wrapper classes

class GithubAPI:
def __init__(self, token=None):
self.base_url = "https://api.github.com"
self.headers = {"Authorization": f"token {token}" if token else None}

def get_user(self, username):
import requests
response = requests.get(f"{self.base_url}/users/{username}", headers=self.headers)
return response.json()

api = GithubAPI()
print(f"User data: {api.get_user('python')['login']}")--OUTPUT--User data: python

Creating a wrapper class like GithubAPI is a smart way to organize your API logic. It bundles all the necessary details—such as the base URL and authentication headers—into a single, reusable object. This keeps your main application code much cleaner and easier to maintain.

  • The __init__ method centralizes configuration. It sets the base_url and prepares authentication headers, so you don't have to repeat this for every request.
  • Methods like get_user abstract the raw API calls. You can fetch data with a simple method call, like api.get_user('python'), instead of manually building a requests call each time.

Handling pagination and rate limits

import requests

def get_paginated_data(url, page=1, per_page=30):
response = requests.get(f"{url}?page={page}&per_page={per_page}")
data = response.json()

remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
print(f"Items received: {len(data)}, API calls remaining: {remaining}")
return data

items = get_paginated_data("https://api.github.com/repos/python/cpython/issues")--OUTPUT--Items received: 30, API calls remaining: 59

APIs often break up large result sets into smaller chunks, a process called pagination. The function get_paginated_data handles this by passing page and per_page as URL parameters, letting you request a specific slice of data. This is essential when working with endpoints that return thousands of items.

You also need to respect API rate limits—the number of requests you can make in a given time. The code shows how to do this proactively.

  • The response.headers object contains useful metadata from the server.
  • By checking a header like X-RateLimit-Remaining, your application can track its usage and avoid being temporarily blocked for making too many calls.

Move faster with Replit

Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.

The API techniques you've learned here can be turned into production-ready tools with the Agent. For example, you could build:

  • A GitHub project dashboard that fetches and displays real-time issue data and contributor activity.
  • A price comparison tool that concurrently pulls product information from multiple e-commerce APIs.
  • A content aggregator that uses authentication to post curated articles to your social media accounts.

Describe your app idea, and Replit Agent will write the code, test it, and deploy it for you. Build your next API-driven application with Replit Agent.

Common errors and challenges

Working with APIs means preparing for network errors, slow responses, and unexpected data formats that can disrupt your application's flow.

  • Handling API request errors with try-except
    API calls can fail for reasons outside your control, like a network outage or an invalid URL. Wrapping your request in a try-except block prevents these issues from crashing your entire program. By catching exceptions, such as the general requests.exceptions.RequestException, you can handle the error gracefully—perhaps by logging it or retrying the request.
  • Fixing timeout issues in API calls
    An API might take too long to respond, leaving your application hanging. You can prevent this by setting a timeout parameter in your request, like requests.get(url, timeout=5). If the server doesn't respond within five seconds, the library raises a requests.exceptions.Timeout error, which you can catch to keep your app responsive.
  • Dealing with missing JSON keys in API responses
    API data isn't always consistent; a key you expect might be missing from the response, causing a KeyError. Instead of accessing data directly with square brackets, use the dictionary's .get() method, like data.get('key'). This approach safely returns None if the key doesn't exist, making your code more resilient to unpredictable API structures.

Handling API request errors with try-except

Your application can't always count on a successful API connection. Network problems or a simple typo in the URL can cause the request to fail, which will halt your script if you don't handle it. The following code demonstrates this exact scenario.

import requests

response = requests.get("https://api.nonexistent-domain.com/data")
data = response.json()
print(f"User: {data['name']}")

The script attempts to contact a domain that doesn't exist, so the requests.get() call fails and raises an exception. See how to handle this gracefully and prevent your application from crashing in the code below.

import requests

try:
response = requests.get("https://api.nonexistent-domain.com/data", timeout=5)
response.raise_for_status()
data = response.json()
print(f"User: {data['name']}")
except requests.exceptions.RequestException as e:
print(f"API request failed: {e}")

The solution wraps the API call in a try-except block to catch potential failures. A key addition is response.raise_for_status(), which checks for bad responses like 404s or 500s and raises an error if one occurs. By catching requests.exceptions.RequestException, your code can handle any network-related issue—from a bad URL to a server error—without crashing. This defensive approach is crucial whenever you're interacting with external services you don't control.

Fixing timeout issues in API calls

Sometimes an API takes too long to respond, leaving your application stuck waiting indefinitely. This can happen if the server is overloaded or experiencing issues. The code below shows what happens when a request is made to a slow server without a timeout.

import requests

response = requests.get("https://api.slow-server.com/data")
data = response.json()
print(f"Retrieved {len(data)} items")

The requests.get() call will wait indefinitely for a response from the slow server, freezing your application. See how to prevent this and keep your program responsive in the corrected code below.

import requests

response = requests.get("https://api.slow-server.com/data", timeout=5)
data = response.json()
print(f"Retrieved {len(data)} items")

By adding the timeout=5 parameter, you instruct requests to wait no more than five seconds for the server to respond. This simple change prevents your application from freezing indefinitely when an API is slow or unresponsive.

If the server fails to reply within that time, the library raises a Timeout exception that you can catch and handle. It’s a crucial safeguard to implement for any external network call to keep your application reliable.

Dealing with missing JSON keys in API responses

API data can be inconsistent. A field you expect, like a user's bio, might be missing if it's empty. Directly accessing a non-existent key with square brackets will raise a KeyError and crash your script. The code below shows what happens.

import requests

response = requests.get("https://api.github.com/users/nonexistent_user")
data = response.json()
print(f"Username: {data['login']}, Bio: {data['bio']}")

The API call for a nonexistent user returns an error message, not a profile. Attempting to access the 'login' or 'bio' keys on this response causes the script to fail. Check the corrected code below for a solution.

import requests

response = requests.get("https://api.github.com/users/nonexistent_user")
data = response.json()
username = data.get('login', 'Unknown')
bio = data.get('bio', 'No bio available')
print(f"Username: {username}, Bio: {bio}")

The solution replaces unsafe bracket access with the dictionary’s .get() method. This is a safer way to retrieve data. If a key like 'login' or 'bio' is missing from the response, .get() returns the default value you specify instead of crashing the program. You should use this technique whenever an API might return optional fields or different structures for error responses, making your application more robust.

Real-world applications

With robust error handling and advanced techniques in your toolkit, you can confidently build useful, real-world applications.

Creating a currency converter with exchange rate API

By making a single GET request to a public exchange rate API, you can instantly fetch the latest conversion rates and build a useful currency converter.

import requests

amount = 100
from_currency = "USD"
to_currency = "EUR"
response = requests.get(f"https://open.er-api.com/v6/latest/{from_currency}")
rates = response.json()["rates"]
converted = amount * rates[to_currency]
print(f"{amount} {from_currency} = {converted:.2f} {to_currency}")

This script shows a practical API use case. It builds the request URL dynamically with an f-string, using the from_currency variable to fetch rates against a specific base currency. After receiving the data, the code dives straight into the JSON response to get the conversion rates.

  • The expression response.json()["rates"] is a direct way to access the nested dictionary containing all currency values.
  • It then uses the to_currency variable as a key to pull the correct rate and calculate the final converted amount.

Automating GitHub issue tracking with the API

By combining the GitHub API with Python's datetime library, you can create powerful automations to monitor repository health, like pulling a list of recently opened issues.

import requests
import datetime

repo = "python/cpython"
created_after = datetime.datetime.now() - datetime.timedelta(days=7)
date_str = created_after.strftime("%Y-%m-%d")
response = requests.get(
f"https://api.github.com/repos/{repo}/issues",
params={"state": "open", "since": date_str}
)
issues = response.json()
print(f"New issues in the last week: {len(issues)}")
print(f"Latest issue: {issues[0]['title'] if issues else 'None'}")

This script automates fetching recent issues from a GitHub repository. It dynamically creates a filter date by using the datetime library to calculate the date from exactly one week ago. This date is then passed to the API through the params dictionary to refine the search.

  • The since parameter tells the API to only return issues created after that date.
  • The state: "open" filter further narrows the results to only include active issues.

This approach lets you request precisely the data you need. The script finishes by safely printing the newest issue's title, checking first if any issues were returned.

Get started with Replit

Turn these API techniques into a real tool. Tell Replit Agent to “build a currency converter using a public exchange rate API” or “create a dashboard that tracks new GitHub issues for a repository.”

The Agent writes the code, tests for common errors, and deploys your application for you. Start building with Replit.

Get started free

Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.

Get started for free

Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.