How to use an API in Python

Learn to use APIs in Python with this guide. Explore different methods, get tips, see real-world applications, and learn to debug common errors.

How to use an API in Python
Published on: 
Fri
Feb 13, 2026
Updated on: 
Mon
Apr 13, 2026
The Replit Team

APIs allow applications to communicate and share data. Python's simple syntax and powerful libraries make it a top choice to connect with APIs for countless web services and data sources.

In this article, you'll explore techniques and tips to use APIs. You will find real-world applications and receive advice to debug your code for successful API integration.

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, abstracting away the complexities of network communication. The code uses requests.get() to fetch data from the GitHub API endpoint for the user "python." This function sends a GET request, which is the conventional method for retrieving information from a web server.

Once the server sends back a response, the .json() method is crucial. It decodes the response's JSON-formatted text into a native Python dictionary. This conversion makes the data immediately accessible, allowing you to work with it using familiar dictionary keys like data['login'].

Common API interaction techniques

Building on that basic request, you'll often need to customize your interactions with parameters, headers, and authentication to get the exact data you need.

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

You can customize requests using parameters and headers. The params argument refines your query, letting you filter or sort results without manually building a complex URL. The headers dictionary sends metadata along with your request.

  • The params dictionary in the example searches for repositories with "python" and sorts them by stars.
  • The headers tell the server what kind of response you want ("Accept": "application/json") and identify your application ("User-Agent"), which is often required by APIs.

The requests library handles the encoding for you, keeping your code clean.

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 great for accessing data, sometimes you need to inspect the raw JSON structure. The built-in json library's dumps() function handles this by converting dictionaries to JSON, transforming a Python dictionary back into a formatted JSON string.

  • Setting indent=2 adds whitespace to create a "pretty-printed" output that's easy for humans to read.
  • This technique is invaluable for debugging and understanding the layout of nested data returned by an API.

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 require authentication to access protected data, and the requests library offers straightforward ways to handle this.

  • For basic access, you can pass a requests.auth.HTTPBasicAuth object with your username and password to the auth parameter.
  • Alternatively, many modern APIs use bearer tokens. You'd include this token in the headers dictionary with the Authorization key.

After the request, checking the response.ok attribute is a quick way to verify if your authentication succeeded.

Advanced API techniques

Now that you've handled basic requests, you'll need more advanced techniques to manage performance, organize your code, and work around API limitations.

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, waiting for each one to finish is inefficient. The aiohttp library solves this by enabling asynchronous requests, so your program doesn't have to pause. It works with Python's asyncio to handle multiple network operations at once in a memory-efficient way.

  • The async and await keywords define non-blocking functions that can run in the background.
  • aiohttp.ClientSession manages a pool of connections for making requests efficiently.
  • asyncio.gather is the key—it launches multiple API calls concurrently and waits for them all to complete, dramatically speeding up data collection.

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

An API wrapper class, like the GithubAPI example, organizes your code by bundling related API logic into a single, reusable object using techniques for creating classes in Python. This approach keeps your main script clean and makes interacting with the API much more intuitive.

  • The __init__ method centralizes configuration, setting up details like the base URL and authentication headers just once.
  • Methods such as get_user abstract away the request logic. You can simply call api.get_user('python') without repeatedly building URLs or handling headers.
  • This structure improves maintainability—if the API changes, you only need to update the class in one place.

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 return large result sets in chunks. This process is called pagination. The get_paginated_data function handles this by using URL parameters like page and per_page to request a specific slice of data, keeping your requests manageable.

  • Most APIs also enforce rate limits, which cap how many requests you can make in a given time.
  • You can track your usage by checking the response headers. The code inspects response.headers.get('X-RateLimit-Remaining') to see how many calls you have left, which is essential for building reliable applications.

Move faster with Replit

Replit is an AI-powered development platform that comes with all Python dependencies pre-installed, so you can skip setup and start coding instantly. You can move from learning individual techniques to building complete apps with Agent 4, which handles everything from writing code to connecting APIs and deployment.

Instead of piecing together code, describe the app you want to build and the Agent will take it from idea to a working product, like:

  • A repository tracker that fetches and displays the most starred Python projects from GitHub, automatically handling paginated results.
  • A developer comparison tool that asynchronously pulls follower and repository counts for multiple GitHub users at once.
  • A user info exporter that extracts specific details like location and bio from a list of usernames and saves the data.

Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.

Common errors and challenges

You'll inevitably face challenges like request errors, timeouts, and missing data, but Python provides straightforward ways to handle them with code repair techniques.

Handling API request errors with try-except

API requests don't always succeed. You might encounter network problems or try to access a nonexistent endpoint. Without error handling, a failed requests.get() call will likely crash your application. The following code demonstrates this exact scenario with an invalid URL.

import requests

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

The requests.get() call to a nonexistent domain raises an exception, which immediately halts the script. The following code shows how to catch this error and prevent your application from crashing.

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}")

To prevent crashes, you should wrap your API calls in a try-except block using proper try and except blocks. This solution is more robust because it anticipates potential failures.

  • It sets a timeout on the request so your program doesn’t hang indefinitely waiting for a slow or nonexistent server.
  • The response.raise_for_status() method actively checks for HTTP errors—like 404 or 500—and raises an exception if one occurs.
  • The except block catches any requests.exceptions.RequestException, letting you handle the failure gracefully instead of stopping the script.

Fixing timeout issues in API calls

If an API server is slow to respond, your request can hang indefinitely and freeze your application. By default, the requests library doesn't have a timeout, so it will wait forever. The code below shows this in action with a slow server.

import requests

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

The script hangs because the requests.get() call waits indefinitely for the slow server to respond. The following code demonstrates how to gracefully handle such delays and keep your application responsive.

import requests

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

The fix is simple: add a timeout parameter to your requests.get() call. Setting timeout=5 instructs the library to wait only five seconds for a response before giving up. If the server is too slow, a Timeout exception is raised, preventing your script from hanging. This is crucial for any API call, as network conditions and server load are often unpredictable, and it keeps your application responsive.

Dealing with missing JSON keys in API responses

APIs don't always return every piece of data you expect—a user might not have a bio, or a field could be optional. If you try to access a key that doesn't exist in the JSON response, your script will crash with a KeyError. The following code demonstrates this exact issue.

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 returns an error message for a nonexistent user, so the response object lacks a bio key. This triggers a KeyError. The following code shows how to safely handle these cases without crashing your script.

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}")

To avoid a KeyError, use the dictionary’s .get() method instead of bracket notation. It allows you to specify a default value to use if a key is missing.

  • For example, data.get('bio', 'No bio available') returns the bio if it exists, or the default string if it doesn't.

This is essential when dealing with optional API fields or error responses, ensuring your code handles incomplete data gracefully without crashing.

Real-world applications

With these robust techniques for interacting with APIs, you can now move from theory to practice and build useful, data-driven tools through vibe coding.

Creating a currency converter with exchange rate API

You can put your API skills to work by building a currency converter that pulls live data from a public exchange rate service.

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 fetches live currency exchange rates from a public API. It’s a practical example of how to use variables to build a dynamic request URL, inserting the from_currency to get the right data.

  • Once the requests.get() call returns a response, the code immediately parses the JSON.
  • It accesses the nested "rates" dictionary to find the specific conversion value for the to_currency.
  • The final converted amount is calculated by multiplying the initial amount with the target currency's rate.

Automating GitHub issue tracking with the API

You can also use the GitHub API to automate project management by tracking new issues in a repository.

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 combines the datetime and requests libraries to fetch time-sensitive data from the GitHub API, demonstrating techniques for working with time in Python. It dynamically calculates a date from one week ago to define a specific timeframe for the query.

  • The strftime function formats this date into the YYYY-MM-DD string required by the API's since parameter.
  • This parameter, along with state: "open", is passed in the params dictionary to filter the results for open issues.
  • The code then displays a count of the new issues and the title of the first one returned.

Get started with Replit

Now, turn these techniques into a real tool. Describe what you want to build to Replit Agent, like “a currency converter for USD, EUR, and JPY” or “a dashboard for new GitHub issues.”

The Agent writes the code, tests for errors, and deploys your application, handling the entire development cycle for you. Start building with Replit.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.

Build your first app today

Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.