Python API Automation: Complete Requests Library Tutorial 2026
You need data from an API. You could manually download JSON files every day, or you could write a Python script that fetches, processes, and stores that data automatically—running on autopilot while you sleep.
API automation is the secret weapon of productive developers and data professionals. Once you master it, you can integrate any service into your workflows: pull CRM data, send Slack notifications, fetch weather forecasts, monitor website uptime, or sync data between platforms.
Today you'll learn the requests library—Python's most popular tool for API automation—and build real-world integrations from scratch.
What You'll Learn
- How APIs work and why they're essential for automation
- Making GET, POST, PUT, and DELETE requests
- Handling authentication (API keys, OAuth, tokens)
- Processing JSON responses and handling errors
- Building production-ready API clients
- Real-world project: Automated weather alerts system
Prerequisites: Basic Python knowledge (variables, functions, loops). No API experience needed.
What Are APIs and Why Automate Them?
API (Application Programming Interface) = A way for applications to talk to each other programmatically.
Instead of clicking through a website, you send code requests and receive structured data back (usually JSON).
Why automate APIs:
- Consistency: Same results every time, no human error
- Speed: Process thousands of requests in seconds
- Integration: Connect different services together
- Scheduling: Run automatically at specific times
- Scalability: Handle volume impossible for humans
Real-world examples:
- Automatically pull customer data from Salesforce into Excel
- Send Slack notifications when server monitoring API detects issues
- Fetch daily stock prices and log them to a database
- Post social media updates across multiple platforms
- Sync contacts between CRM and email marketing tools
According to a 2025 developer survey, API automation saves developers an average of 8.5 hours per week on data integration tasks.
Setting Up: Installing the Requests Library
The requests library is Python's most popular HTTP library. It's simple, powerful, and production-ready.
Installation:
1pip install requests
Verify installation:
1import requests2print(requests.__version__) # Should print version number like 2.31.0
Why use requests instead of urllib:
- Cleaner, more intuitive syntax
- Better error handling
- Built-in JSON support
- Session management
- Automatic connection pooling
If you're on Python 3.9+, requests isn't in the standard library, so installation is required.
Making Your First API Request: GET Basics
Let's start simple: fetch data from a public API.
Example: Get random user data from JSONPlaceholder (a fake REST API for testing).
1import requests23# Make a GET request4response = requests.get('https://jsonplaceholder.typicode.com/users/1')56# Check if request was successful7if response.status_code == 200:8 # Parse JSON data9 user_data = response.json()10 print(f"Name: {user_data['name']}")11 print(f"Email: {user_data['email']}")12 print(f"Company: {user_data['company']['name']}")13else:14 print(f"Error: {response.status_code}")
Output:
Name: Leanne Graham Email: Sincere@april.biz Company: Romaguera-Crona
Breaking it down:
requests.get(url)- Sends HTTP GET request to the URLresponse.status_code- HTTP status code (200 = success)response.json()- Parses JSON response into Python dictionary- Access data like any Python dict:
user_data['name']
HTTP Status Codes You'll See:
- 200: Success - request worked perfectly
- 201: Created - resource successfully created (POST requests)
- 400: Bad Request - your request has errors
- 401: Unauthorized - authentication failed
- 403: Forbidden - you don't have permission
- 404: Not Found - endpoint doesn't exist
- 429: Too Many Requests - you hit rate limits
- 500: Server Error - API's problem, not yours
Sending Data: POST Requests
POST requests send data to APIs to create new resources.
Example: Create a new post on JSONPlaceholder.
1import requests23# Data to send4new_post = {5 'title': 'Automated API Post',6 'body': 'This post was created via Python automation',7 'userId': 18}910# Make POST request11response = requests.post(12 'https://jsonplaceholder.typicode.com/posts',13 json=new_post # Automatically sets Content-Type: application/json14)1516if response.status_code == 201: # 201 = Created17 created_post = response.json()18 print(f"Post created with ID: {created_post['id']}")19 print(created_post)20else:21 print(f"Error: {response.status_code}")
Key differences from GET:
- Use
requests.post()instead ofrequests.get() - Pass data via
json=parameter - Success code is usually 201 (Created) instead of 200
When to use POST:
- Creating new records (users, posts, orders)
- Submitting forms
- Uploading files
- Triggering actions (send email, start process)
Updating Data: PUT and PATCH Requests
PUT = Replace entire resource
PATCH = Update specific fields
Example: Update a post (PUT):
1import requests23updated_post = {4 'id': 1,5 'title': 'Updated Title',6 'body': 'Completely new content',7 'userId': 18}910response = requests.put(11 'https://jsonplaceholder.typicode.com/posts/1',12 json=updated_post13)1415if response.status_code == 200:16 print("Post updated successfully")17 print(response.json())
Example: Partial update (PATCH):
1import requests23# Only update title, leave rest unchanged4partial_update = {5 'title': 'Just changing the title'6}78response = requests.patch(9 'https://jsonplaceholder.typicode.com/posts/1',10 json=partial_update11)1213if response.status_code == 200:14 print("Title updated")15 print(response.json())
PUT vs PATCH:
- PUT: Must send all fields, replaces entire resource
- PATCH: Send only fields you want to change
- Best practice: Use PATCH for partial updates to avoid accidentally deleting fields
Deleting Data: DELETE Requests
Remove resources from the API.
1import requests23response = requests.delete('https://jsonplaceholder.typicode.com/posts/1')45if response.status_code == 200:6 print("Post deleted successfully")7elif response.status_code == 404:8 print("Post not found - already deleted?")
When to use DELETE:
- Removing records (users, posts, files)
- Canceling subscriptions
- Clearing cache
- Revoking access
Warning: DELETE is usually permanent. Double-check before automating deletions.
Authentication: API Keys and Tokens
Most real APIs require authentication to identify and authorize you.
Method 1: API Key in Query Parameters
1import requests23API_KEY = 'your_api_key_here'45response = requests.get(6 'https://api.example.com/data',7 params={'api_key': API_KEY} # Adds ?api_key=your_api_key_here to URL8)
Method 2: API Key in Headers (More Common)
1import requests23API_KEY = 'your_api_key_here'45headers = {6 'Authorization': f'Bearer {API_KEY}'7}89response = requests.get(10 'https://api.example.com/data',11 headers=headers12)
Method 3: Basic Authentication (Username + Password)
1import requests23response = requests.get(4 'https://api.example.com/data',5 auth=('username', 'password') # Requests handles encoding6)
Method 4: OAuth Token
1import requests23ACCESS_TOKEN = 'your_oauth_token'45headers = {6 'Authorization': f'Bearer {ACCESS_TOKEN}'7}89response = requests.get(10 'https://api.example.com/data',11 headers=headers12)
Security best practice: Never hardcode API keys in scripts. Use environment variables:
1import os2import requests34API_KEY = os.getenv('MY_API_KEY') # Set via export MY_API_KEY=xxx56if not API_KEY:7 raise ValueError("API_KEY environment variable not set")89headers = {'Authorization': f'Bearer {API_KEY}'}10response = requests.get('https://api.example.com/data', headers=headers)
Handling JSON Responses Like a Pro
APIs return JSON. Here's how to extract what you need:
Simple JSON Response
1response = requests.get('https://api.example.com/user/123')2data = response.json()34# Access nested data5user_name = data['user']['name']6email = data['user']['contact']['email']
Handling Missing or Optional Fields
1data = response.json()23# Bad: Crashes if 'optional_field' doesn't exist4value = data['optional_field']56# Good: Use .get() with default7value = data.get('optional_field', 'default_value')89# Better: Safely navigate nested data10email = data.get('user', {}).get('contact', {}).get('email', 'no-email')
Processing Lists in JSON
1response = requests.get('https://api.example.com/users')2users = response.json()34# Loop through list of users5for user in users:6 print(f"{user['name']} - {user['email']}")78# Filter with list comprehension9active_users = [u for u in users if u['status'] == 'active']1011# Extract specific fields12emails = [user['email'] for user in users]
Pretty Printing JSON for Debugging
1import json23response = requests.get('https://api.example.com/data')4data = response.json()56# Pretty print for readability7print(json.dumps(data, indent=2))
Error Handling: Build Robust API Clients
Never trust that APIs will always work. Handle errors gracefully.
Basic Error Handling
1import requests23try:4 response = requests.get('https://api.example.com/data', timeout=10)5 response.raise_for_status() # Raises exception for 4xx/5xx status codes67 data = response.json()8 # Process data...910except requests.exceptions.HTTPError as e:11 print(f"HTTP error occurred: {e}")12except requests.exceptions.ConnectionError:13 print("Failed to connect to API")14except requests.exceptions.Timeout:15 print("Request timed out")16except requests.exceptions.RequestException as e:17 print(f"An error occurred: {e}")
Production-Ready Error Handling with Retries
1import requests2import time34def make_api_request(url, max_retries=3):5 """Make API request with exponential backoff retry logic"""67 for attempt in range(max_retries):8 try:9 response = requests.get(url, timeout=10)10 response.raise_for_status()11 return response.json()1213 except requests.exceptions.HTTPError as e:14 if e.response.status_code == 429: # Rate limit15 wait_time = 2 ** attempt # Exponential backoff: 1s, 2s, 4s16 print(f"Rate limited. Waiting {wait_time}s before retry...")17 time.sleep(wait_time)18 elif e.response.status_code >= 500: # Server error19 print(f"Server error. Retry {attempt + 1}/{max_retries}")20 time.sleep(2)21 else:22 raise # Don't retry client errors (4xx)2324 except requests.exceptions.Timeout:25 print(f"Timeout. Retry {attempt + 1}/{max_retries}")26 time.sleep(2)2728 except requests.exceptions.ConnectionError:29 print(f"Connection error. Retry {attempt + 1}/{max_retries}")30 time.sleep(2)3132 raise Exception(f"Failed after {max_retries} retries")3334# Usage35try:36 data = make_api_request('https://api.example.com/data')37 print("Success:", data)38except Exception as e:39 print(f"Fatal error: {e}")
Error handling best practices:
- Always set
timeout(prevents hanging forever) - Use
raise_for_status()to catch HTTP errors - Retry on rate limits and server errors, not client errors
- Implement exponential backoff for retries
- Log errors for debugging
Query Parameters: Filtering and Pagination
APIs often accept query parameters to filter, sort, and paginate results.
Adding Query Parameters
1import requests23# Manual URL building (not recommended)4response = requests.get('https://api.example.com/products?category=electronics&sort=price&limit=50')56# Better: Use params dictionary7params = {8 'category': 'electronics',9 'sort': 'price',10 'limit': 5011}1213response = requests.get('https://api.example.com/products', params=params)
Handling Pagination
Many APIs limit results per request. You need to fetch multiple "pages":
1import requests23def fetch_all_pages(base_url):4 """Fetch all pages from a paginated API"""5 all_data = []6 page = 178 while True:9 response = requests.get(base_url, params={'page': page, 'per_page': 100})10 response.raise_for_status()1112 data = response.json()1314 if not data: # No more data15 break1617 all_data.extend(data)18 page += 11920 print(f"Fetched page {page}, total items: {len(all_data)}")2122 return all_data2324# Usage25all_products = fetch_all_pages('https://api.example.com/products')26print(f"Total products: {len(all_products)}")
Cursor-Based Pagination (Common in Modern APIs)
1import requests23def fetch_with_cursor(base_url):4 """Fetch data using cursor-based pagination"""5 all_data = []6 cursor = None78 while True:9 params = {'limit': 100}10 if cursor:11 params['cursor'] = cursor1213 response = requests.get(base_url, params=params)14 response.raise_for_status()1516 result = response.json()17 all_data.extend(result['data'])1819 cursor = result.get('next_cursor')20 if not cursor: # No more pages21 break2223 return all_data
Sessions: Faster Requests with Connection Pooling
If you're making multiple requests to the same API, use sessions for better performance.
Without sessions (slow - creates new connection each time):
1import requests23for i in range(10):4 response = requests.get(f'https://api.example.com/data/{i}')5 print(response.json())
With sessions (fast - reuses connection):
1import requests23session = requests.Session()4session.headers.update({'Authorization': 'Bearer YOUR_TOKEN'})56for i in range(10):7 response = session.get(f'https://api.example.com/data/{i}')8 print(response.json())910session.close() # Clean up
Benefits of sessions:
- Reuses TCP connections (faster)
- Persists headers, cookies, and auth across requests
- More efficient for multiple requests to same domain
Session best practices:
1import requests23# Use context manager (auto-closes session)4with requests.Session() as session:5 session.headers.update({6 'Authorization': 'Bearer YOUR_TOKEN',7 'User-Agent': 'MyApp/1.0'8 })910 for i in range(10):11 response = session.get(f'https://api.example.com/data/{i}')12 # Process response...
Real-World Project: Automated Weather Alert System
Let's build a complete API automation project that sends you email alerts for bad weather.
What it does:
- Fetches weather forecast from OpenWeatherMap API
- Checks for severe weather conditions
- Sends email alert if bad weather is coming
Step 1: Get API Key Sign up at openweathermap.org for free API key.
Step 2: Install dependencies
1pip install requests python-dotenv
Step 3: Create .env file (store credentials safely):
WEATHER_API_KEY=your_openweathermap_api_key EMAIL_USER=your_email@gmail.com EMAIL_PASSWORD=your_app_password ALERT_EMAIL=recipient@example.com
Step 4: The complete script:
1import requests2import os3from dotenv import load_dotenv4import smtplib5from email.mime.text import MIMEText6from email.mime.multipart import MIMEMultipart78# Load environment variables9load_dotenv()1011class WeatherAlertSystem:12 def __init__(self, api_key, city):13 self.api_key = api_key14 self.city = city15 self.base_url = "https://api.openweathermap.org/data/2.5/forecast"1617 def get_weather_forecast(self):18 """Fetch 5-day weather forecast"""19 params = {20 'q': self.city,21 'appid': self.api_key,22 'units': 'metric' # Celsius23 }2425 try:26 response = requests.get(self.base_url, params=params, timeout=10)27 response.raise_for_status()28 return response.json()29 except requests.exceptions.RequestException as e:30 print(f"Error fetching weather: {e}")31 return None3233 def check_severe_weather(self, forecast_data):34 """Check for severe weather conditions"""35 alerts = []3637 if not forecast_data:38 return alerts3940 # Check next 24 hours (8 forecasts at 3-hour intervals)41 for forecast in forecast_data['list'][:8]:42 weather = forecast['weather'][0]43 main_weather = weather['main']44 description = weather['description']45 temp = forecast['main']['temp']4647 # Define severe weather conditions48 if main_weather in ['Thunderstorm', 'Snow', 'Extreme']:49 alerts.append(f"⚠️ {main_weather}: {description}")5051 if temp > 35:52 alerts.append(f"🔥 Extreme heat: {temp}°C")53 elif temp < -10:54 alerts.append(f"❄️ Extreme cold: {temp}°C")5556 # Check wind speed57 wind_speed = forecast['wind']['speed']58 if wind_speed > 15: # m/s (strong wind)59 alerts.append(f"💨 Strong winds: {wind_speed} m/s")6061 return list(set(alerts)) # Remove duplicates6263 def send_email_alert(self, alerts):64 """Send email alert about severe weather"""65 if not alerts:66 print("No severe weather detected")67 return6869 sender = os.getenv('EMAIL_USER')70 password = os.getenv('EMAIL_PASSWORD')71 recipient = os.getenv('ALERT_EMAIL')7273 # Create email74 msg = MIMEMultipart()75 msg['From'] = sender76 msg['To'] = recipient77 msg['Subject'] = f'⚠️ Weather Alert for {self.city}'7879 body = f"""80 Severe weather detected in {self.city} for the next 24 hours:8182 {chr(10).join(alerts)}8384 Stay safe!8586 --87 Automated Weather Alert System88 """8990 msg.attach(MIMEText(body, 'plain'))9192 # Send email93 try:94 with smtplib.SMTP('smtp.gmail.com', 587) as server:95 server.starttls()96 server.login(sender, password)97 server.send_message(msg)98 print(f"Alert sent to {recipient}")99 except Exception as e:100 print(f"Error sending email: {e}")101102 def run(self):103 """Main execution"""104 print(f"Checking weather for {self.city}...")105106 forecast = self.get_weather_forecast()107 alerts = self.check_severe_weather(forecast)108109 if alerts:110 print(f"Found {len(alerts)} weather alerts")111 self.send_email_alert(alerts)112 else:113 print("No severe weather detected - all clear!")114115# Run the system116if __name__ == "__main__":117 api_key = os.getenv('WEATHER_API_KEY')118 city = "San Francisco" # Change to your city119120 alert_system = WeatherAlertSystem(api_key, city)121 alert_system.run()
Step 5: Automate with cron/Task Scheduler
Run this script automatically every morning:
Linux/Mac (crontab):
1# Run daily at 7 AM20 7 * * * /usr/bin/python3 /path/to/weather_alert.py
Windows (Task Scheduler):
Create task that runs python weather_alert.py at 7:00 AM daily.
Best Practices for Production API Automation
1. Use Environment Variables for Credentials
1import os23API_KEY = os.getenv('API_KEY')4if not API_KEY:5 raise ValueError("API_KEY environment variable not set")
2. Implement Rate Limiting
Respect API rate limits to avoid getting banned:
1import time23class RateLimitedAPI:4 def __init__(self, calls_per_minute=60):5 self.calls_per_minute = calls_per_minute6 self.min_interval = 60.0 / calls_per_minute7 self.last_call = 089 def wait_if_needed(self):10 elapsed = time.time() - self.last_call11 if elapsed < self.min_interval:12 time.sleep(self.min_interval - elapsed)13 self.last_call = time.time()1415 def make_request(self, url):16 self.wait_if_needed()17 return requests.get(url)
3. Log Everything
1import logging23logging.basicConfig(4 level=logging.INFO,5 format='%(asctime)s - %(levelname)s - %(message)s',6 handlers=[7 logging.FileHandler('api_automation.log'),8 logging.StreamHandler()9 ]10)1112logger = logging.getLogger(__name__)1314try:15 response = requests.get(url)16 logger.info(f"Successfully fetched data from {url}")17except Exception as e:18 logger.error(f"Failed to fetch {url}: {e}")
4. Validate Response Data
1def validate_response(data):2 """Ensure response has expected structure"""3 required_fields = ['id', 'name', 'email']45 for field in required_fields:6 if field not in data:7 raise ValueError(f"Missing required field: {field}")89 return True1011response = requests.get(url)12data = response.json()1314if validate_response(data):15 # Process data...
5. Use Type Hints for Better Code
1import requests2from typing import Dict, List, Optional34def fetch_user_data(user_id: int, api_key: str) -> Optional[Dict]:5 """Fetch user data from API67 Args:8 user_id: User ID to fetch9 api_key: API authentication key1011 Returns:12 Dictionary with user data or None if error13 """14 try:15 response = requests.get(16 f'https://api.example.com/users/{user_id}',17 headers={'Authorization': f'Bearer {api_key}'}18 )19 response.raise_for_status()20 return response.json()21 except requests.exceptions.RequestException:22 return None
Common API Automation Mistakes
Mistake 1: Not Handling Rate Limits
Problem: Making too many requests too fast gets you banned
Solution: Implement rate limiting and exponential backoff
Mistake 2: Hardcoding API Keys
Problem: Security risk if code is shared or pushed to Git
Solution: Use environment variables and .env files (add .env to .gitignore)
Mistake 3: No Error Handling
Problem: Script crashes on first error
Solution: Use try/except blocks and retry logic
Mistake 4: Not Reading API Documentation
Problem: Misusing endpoints, missing features
Solution: Read docs first, especially for authentication, rate limits, and pagination
Mistake 5: Synchronous Requests for Many URLs
Problem: Fetching 100 URLs one-by-one takes forever
Solution: Use concurrent.futures or asyncio for parallel requests:
1from concurrent.futures import ThreadPoolExecutor2import requests34urls = [f'https://api.example.com/data/{i}' for i in range(100)]56def fetch_url(url):7 response = requests.get(url)8 return response.json()910# Fetch 10 URLs at once11with ThreadPoolExecutor(max_workers=10) as executor:12 results = list(executor.map(fetch_url, urls))1314print(f"Fetched {len(results)} results")
Frequently Asked Questions
What's the difference between requests and urllib?
requests is a third-party library built on top of urllib3 with a much simpler, more intuitive API. Use requests for almost everything—it's the industry standard. urllib is built-in but more complex.
How do I download files from APIs?
1response = requests.get(url, stream=True)2with open('downloaded_file.pdf', 'wb') as f:3 for chunk in response.iter_content(chunk_size=8192):4 f.write(chunk)
Can I use requests for uploading files?
Yes! Use the files parameter:
1files = {'file': open('document.pdf', 'rb')}2response = requests.post(url, files=files)
How do I handle APIs that return XML instead of JSON?
Use response.text instead of response.json(), then parse with xml.etree.ElementTree or lxml:
1import xml.etree.ElementTree as ET2response = requests.get(url)3root = ET.fromstring(response.text)
What if an API requires a client certificate?
Pass the certificate file path to the cert parameter:
1response = requests.get(url, cert=('/path/to/cert.pem', '/path/to/key.pem'))
Conclusion
API automation with Python's requests library is a superpower. You can now:
- Fetch data from any API
- Send, update, and delete data programmatically
- Handle authentication securely
- Build robust error handling and retry logic
- Create production-ready automation scripts
Start small: pick one API you use manually and automate it. Maybe it's pulling CRM data into a spreadsheet, or posting to Slack, or monitoring a website. Build that automation, run it daily, and watch the time savings compound.
The skills you learned today apply to thousands of APIs: GitHub, Twitter, Salesforce, Stripe, SendGrid, Twilio—any service with an API is now automatable.
Next steps: explore API documentation for services you use, experiment with different endpoints, and chain multiple APIs together into powerful workflows.
Related articles: Python Salesforce API Integration for CRM Automation, Python Slack Bot Automation Tutorial
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
