How to call an API in Python

Learn how to call an API in Python. This guide covers different methods, tips, real-world examples, and common error debugging.

How to call an API in Python
Published on: 
Thu
Feb 12, 2026
Updated on: 
Mon
Apr 13, 2026
The Replit Team

You can use Python to call APIs, a core skill for developers. This allows applications to fetch data from external services and unlocks powerful integrations for any project you build.

In this article, you'll find key techniques, practical tips, and real-world applications. You'll also get debugging advice to help you confidently integrate external data into your Python projects.

Using the requests library for a basic API call

import requests

response = requests.get("https://jsonplaceholder.typicode.com/posts/1")
data = response.json()
print(data)--OUTPUT--{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}

The requests library is a popular choice for making HTTP requests because it simplifies the process. In this example, requests.get() sends a request to the specified URL, which is a placeholder API endpoint for a single post. This function returns a Response object containing all the information the server sent back.

The most important step is using the .json() method. This method takes the raw JSON data from the response and automatically converts it into a Python dictionary. This makes the data immediately accessible and easy to work with in your code, without needing to parse it manually.

Basic API techniques

With a basic GET request covered, you're ready to tackle more common API tasks like filtering results, sending data, and handling authentication.

Working with query parameters using requests

import requests

params = {"postId": 1, "limit": 3}
response = requests.get("https://jsonplaceholder.typicode.com/comments", params=params)
comments = response.json()
print(f"Found {len(comments)} comments")
print(comments[0]["email"])--OUTPUT--Found 3 comments
Eliseo@gardner.biz

Query parameters let you filter or customize the data you receive from an API. Instead of manually building the URL string, the requests library simplifies this with the params argument in the get() function.

  • You pass a dictionary of key-value pairs to params.
  • The library automatically formats and appends them to the URL for you.

In this example, {"postId": 1, "limit": 3} asks the API for comments associated with post ID 1 and limits the results to three, making your request more specific and efficient.

Making POST requests with JSON data

import requests

new_post = {
"title": "My New Post",
"body": "This is the content of my post",
"userId": 1
}
response = requests.post("https://jsonplaceholder.typicode.com/posts", json=new_post)
print(f"Status code: {response.status_code}")
print(response.json())--OUTPUT--Status code: 201
{'title': 'My New Post', 'body': 'This is the content of my post', 'userId': 1, 'id': 101}

While GET requests fetch data, POST requests send data to create something new on the server. In this case, you're creating a new post.

  • First, you define a Python dictionary called new_post with the data for your new entry.
  • Then, you use requests.post(), passing the URL and your dictionary to the json parameter. The library handles converting your dictionary into JSON format.
  • A 201 status code confirms the resource was successfully created. The API's response includes the new post, now with an id assigned by the server.

Handling API authentication

import requests

api_key = "your_api_key_here"
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.get(
"https://api.example.com/data",
headers=headers
)
print(f"Status: {response.status_code}")
print("Authenticated request completed")--OUTPUT--Status: 200
Authenticated request completed

Many APIs require authentication to protect data. This example uses a common method—sending an API key in the request headers. You create a dictionary called headers to hold this information, which proves your identity to the server.

  • The Authorization key is standard for this purpose. Its value often starts with Bearer followed by your unique API key.
  • You pass this dictionary to the headers parameter in your requests.get() call. The library then includes it with your request, granting you access.

Advanced API techniques

Building on those foundational skills, you can now write more powerful API integrations by handling asynchronous calls, creating reusable clients, and implementing robust error handling.

Working with async API calls using aiohttp

import aiohttp
import asyncio

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

async def main():
tasks = [
fetch_data(f"https://jsonplaceholder.typicode.com/posts/{i}")
for i in range(1, 4)
]
results = await asyncio.gather(*tasks)
for result in results:
print(f"Post title: {result['title'][:30]}...")

asyncio.run(main())--OUTPUT--Post title: sunt aut facere repellat provi...
Post title: qui est esse...
Post title: ea molestias quasi exercitatio...

When you need to make many API calls, running them one by one can be slow. The aiohttp library, paired with Python's built-in asyncio, lets you perform these requests concurrently. This means your program doesn't have to wait for one request to finish before starting the next, which saves a lot of time and is memory-efficient.

  • The code uses async and await syntax to define non-blocking network operations.
  • Instead of waiting for each request, asyncio.gather() runs all your API call tasks at the same time.
  • It then collects the results once they are all complete, making your data fetching far more efficient.

Creating a reusable API client class

class ApiClient:
def __init__(self, base_url, api_key=None):
self.base_url = base_url
self.session = requests.Session()
if api_key:
self.session.headers.update({"Authorization": f"Bearer {api_key}"})

def get(self, endpoint, params=None):
url = f"{self.base_url}/{endpoint}"
response = self.session.get(url, params=params)
response.raise_for_status()
return response.json()

client = ApiClient("https://jsonplaceholder.typicode.com")
users = client.get("users")
print(f"Retrieved {len(users)} users")
print(f"First user: {users[0]['name']}")--OUTPUT--Retrieved 10 users
First user: Leanne Graham

Wrapping your API logic in a class like ApiClient keeps your code organized and DRY—Don't Repeat Yourself. This approach centralizes all your API-related functions, making them easier to manage and reuse across your project.

  • The __init__ method sets up a requests.Session() object, which efficiently reuses connections and can store headers for authentication across all requests.
  • Methods like get simplify calls by only requiring an endpoint, while handling URL construction internally.
  • It includes response.raise_for_status(), a crucial step that automatically checks for HTTP errors and stops your code if a request fails.

Implementing error handling and retries

import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retries = Retry(total=3, backoff_factor=0.5, status_forcelist=[500, 502, 503, 504])
session.mount('https://', HTTPAdapter(max_retries=retries))

try:
response = session.get("https://api.example.com/potentially-unstable-endpoint")
response.raise_for_status()
data = response.json()
except requests.exceptions.RequestException as e:
print(f"API request failed: {e}")--OUTPUT--API request failed: HTTPSConnectionPool(host='api.example.com', port=443): Max retries exceeded with url: /potentially-unstable-endpoint

APIs can sometimes fail due to temporary server issues. Instead of letting your application crash, you can build in resilience. This code configures a requests session to automatically retry failed requests—a best practice for creating reliable software.

  • The Retry object specifies the rules. Here, it'll attempt a request up to three times (total=3).
  • A backoff_factor adds a smart delay between retries so you don't overwhelm the server.
  • The status_forcelist ensures it only retries on specific server errors, like 503 Service Unavailable.

Finally, the try...except block catches any error that persists after all retries, preventing a crash. For more complex error scenarios, you can also use automated code repair to identify and fix issues in your API integration code.

Move faster with Replit

Replit is an AI-powered development platform where all Python dependencies are pre-installed, so you can skip setup and start coding instantly. This lets you move from piecing together individual techniques to building complete applications with Agent 4, which handles everything from code and APIs to deployment based on your description.

Instead of just learning the methods, you can describe the app you want to build and let the Agent take it from idea to a working product. For example, you could create:

  • A real-time crypto dashboard that uses GET requests with query parameters to fetch and display live prices for specific coins.
  • A social media scheduler that uses POST requests to automatically publish content to different platforms at set times.
  • A data aggregation tool that uses aiohttp for asynchronous calls to efficiently pull user data from multiple API endpoints.

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

Common errors and challenges

When working with APIs, you'll run into common issues like timeouts, bad server responses, and URL encoding problems.

Handling timeout errors with the timeout parameter

An API server might take too long to respond, leaving your application stuck waiting indefinitely. By default, requests will wait forever for a response, which can freeze your program. The code below shows what happens when a request hangs without a timeout.

import requests

response = requests.get("https://api.example.com/data")
data = response.json()
print("Data retrieved successfully")

The program hangs because the requests.get() call waits indefinitely for an unresponsive server. The following example demonstrates how to prevent your application from getting stuck.

import requests

response = requests.get("https://api.example.com/data", timeout=5)
data = response.json()
print("Data retrieved successfully")

Adding timeout=5 to your requests.get() call is the solution. This parameter sets a deadline for the server to respond, preventing your program from hanging indefinitely.

  • It tells requests to wait no more than five seconds for a response.
  • If the server doesn't reply in time, the library raises a Timeout exception, which you can handle instead of letting your program freeze.

It's a crucial safeguard for any external network request, as you can't predict when a service will be slow or unavailable.

Checking status codes before calling json()

Not all API responses are successful. If you request a non-existent endpoint, the server returns an error, not the JSON data you expect. Calling .json() on such a response will crash your program. The following code demonstrates this common pitfall.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/nonexistent")
data = response.json()
print(data)

The server returns a 404 error, so the response body contains an error message instead of JSON. Calling .json() fails because it can't decode the content. The code below demonstrates the correct way to handle this.

import requests

response = requests.get("https://jsonplaceholder.typicode.com/nonexistent")
if response.status_code == 200:
data = response.json()
print(data)
else:
print(f"Error: {response.status_code}, {response.text[:50]}")

To avoid crashes, always check the API's response before trying to use it. The solution is to wrap your .json() call in a simple if statement. You check if response.status_code is 200 (success). If it is, you proceed. If not, the else block catches the error, printing the status and preventing your app from breaking. This is a simple but crucial step for writing robust code that interacts with any external service.

Handling URL encoding issues with special characters

Handling URL encoding issues with special characters

Special characters like the ampersand (&) or spaces in a URL can break your API request because they have reserved meanings. If you don't encode them properly, the server will misinterpret the URL. The following code shows what happens when a search term breaks the request.

import requests

search_term = "coffee & tea"
response = requests.get(f"https://api.example.com/search?q={search_term}")
results = response.json()
print(f"Found {len(results)} results")

The f-string inserts the & directly into the URL, which the server misreads as a parameter separator. This breaks the query, making it look for q=coffee instead of the full phrase. The code below demonstrates the proper way to build the URL.

import requests
from urllib.parse import quote

search_term = "coffee & tea"
encoded_term = quote(search_term)
response = requests.get(f"https://api.example.com/search?q={encoded_term}")
results = response.json()
print(f"Found {len(results)} results")

The urllib.parse.quote() function solves this by converting special characters like & and spaces into a URL-safe format, such as %26. This ensures the server correctly interprets your entire search term instead of splitting it. You should always use this function when building URLs with user input or any text that might contain special characters. This simple step prevents unexpected behavior and ensures your API requests work as intended.

Real-world applications

Now that you've seen how to handle common errors, you can apply these API skills to build practical, data-driven applications.

Creating a simple URL shortener client with requests

This example puts theory into practice by showing how a single GET request to a public API can power a useful tool like a URL shortener.

import requests

def shorten_url(long_url):
api_url = f"https://tinyurl.com/api-create.php?url={long_url}"
response = requests.get(api_url)
return response.text

original_url = "https://www.example.com/really/long/url/that/is/hard/to/share"
short_url = shorten_url(original_url)
print(f"Original URL: {original_url}")
print(f"Shortened URL: {short_url}")

This function, shorten_url, shows how to interact with a simple text-based API. It constructs the request URL using an f-string, embedding the long URL as a query parameter for the TinyURL service. You could also build this tool using vibe coding to describe what you want rather than writing the code manually.

  • The requests.get() call sends the request to the API endpoint.
  • Instead of parsing JSON, the function returns response.text. This is because the API directly sends back the shortened URL as a plain text string, a common pattern for simple services.

Downloading and analyzing JSON data sets

This example combines an API request with data processing to fetch a complete dataset, parse it, and calculate key statistics.

import requests
import statistics

def analyze_population_data():
url = "https://restcountries.com/v3.1/region/europe?fields=name,population"
response = requests.get(url)
countries = response.json()

populations = [country['population'] for country in countries]

return {
'count': len(populations),
'min': min(populations),
'max': max(populations),
'mean': statistics.mean(populations)
}

stats = analyze_population_data()
print(f"Analyzed {stats['count']} European countries:")
print(f"Smallest population: {stats['min']:,}")
print(f"Largest population: {stats['max']:,}")
print(f"Average population: {stats['mean']:,.0f}")

This function, analyze_population_data, fetches data for European countries from the REST Countries API. It uses a list comprehension to efficiently extract population numbers from the JSON response before calculating basic statistics. Similar data processing patterns are used when reading CSV files for analysis.

  • The API URL includes ?fields=name,population to request only the necessary data, reducing the payload size.
  • A list comprehension, [country['population'] for country in countries], creates a new list containing only population figures for easy analysis.
  • The statistics library is then used to calculate the mean from this list.

Get started with Replit

Turn these techniques into a real tool. Describe what you want to build to Replit Agent, like “build a crypto dashboard that fetches live prices” or “create a tool to post updates to a Discord channel.”

Replit Agent will write the code, test for errors, and deploy your application 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.