Stop Copying API Code from Stack Overflow: Learn REST API Fundamentals for DevOps

Share

You cannot succeed in DevOps without understanding REST APIs.

I see too many DevOps engineers copying API code from Stack Overflow, pasting it into their scripts, and hoping it works. They make API calls without understanding what headers do, why authentication fails, or what those status codes mean.

Every tool you use in DevOps talks to REST APIs. The AWS CLI makes REST API calls to AWS. Terraform makes REST API calls to create infrastructure. kubectl makes REST API calls to Kubernetes. When you need to automate something that doesn’t have a CLI tool, you write Python scripts that make REST API calls.

Understanding REST APIs isn’t optional. It’s fundamental.

Why I’m Teaching This with WordPress and Docker

Most REST API tutorials use public APIs that you can’t control or modify. You’re limited to reading data, and you can’t experiment freely. That’s terrible for learning.

I’m taking a different approach. We’re going to run WordPress locally using Docker, and use its REST API as our learning playground. Here’s why this works:

You control everything. No API keys to manage, no rate limits, no worrying about breaking someone’s production system.

It’s completely local. Everything runs on your laptop. No need for domain names, hosting, or cloud accounts.

It’s a real REST API. WordPress REST API follows standard REST conventions. Learn it, and you understand how to work with AWS, GitHub, Kubernetes, and every other REST API.

You can experiment freely. Create posts, update them, delete them. Break things, fix them, learn by doing.

Here’s what happened to me early in my career: I was automating infrastructure deployments, and I needed to update our internal wiki every time we deployed. The wiki had an API, but I didn’t understand REST APIs. I spent two days trying to figure out why my authentication kept failing, why my POST requests returned 400 errors, why response parsing broke randomly.

If I had understood REST API fundamentals – headers, authentication, request methods, response codes – I would have finished in an hour.

Don’t be that person.

If you want to learn DevOps by building advanced, real-world projects, then consider joining my upcoming Advanced DevOps Bootcamp.

The REST API Fundamentals That Actually Matter

Forget about memorizing every HTTP status code or reading the entire REST specification. Focus on the core concepts you’ll use daily when automating infrastructure tasks. Here are the essentials every DevOps engineer must master:

1. Understanding REST APIs: The Foundation

Before you write a single line of code, you need to understand what a REST API actually is.

What is a REST API?

REST (Representational State Transfer) is just a fancy way of saying “a standardized way for applications to talk to each other over HTTP.”

Think of it like this:

  • Your browser talks to websites using HTTP
  • REST APIs let your code talk to other applications using the same HTTP protocol
  • Instead of getting back HTML for humans to read, you get JSON for your code to process

Real-World Example

When you visit https://example.com/posts In your browser, you see a webpage.

When your Python script visits https://example.com/api/posts, it gets JSON data:

[
  {
    "id": 1,
    "title": "My First Post",
    "content": "Hello World"
  }
]

Your script can now process this data, store it, analyze it, or use it to make decisions.

The Four HTTP Methods You’ll Actually Use

REST APIs use HTTP methods to define actions:

GET – Retrieve data (like reading a file)

  • Get list of posts
  • Get user information
  • Fetch server status

POST – Create new data (like creating a file)

  • Create a new post
  • Add a new user
  • Submit a form

PUT/PATCH – Update existing data (like editing a file)

  • Update post content
  • Modify user details
  • Change configuration

DELETE – Remove data (like deleting a file)

  • Delete a post
  • Remove a user
  • Clean up resources

Real scenario: In our WordPress automation, we use GET to fetch existing posts, POST to create new deployment changelogs, PUT to update them if we made a mistake, and DELETE to remove test posts.

2. Installing and Setting Up Python Requests

The requests library is the standard way to make HTTP calls in Python. It’s clean, simple, and handles all the complexity for you.

Installation

pip install requests

That’s it. No complex setup, no configuration files.

Your First Request

Let’s start with the simplest possible example:

import requests

response = requests.get('https://api.github.com')
print(response.status_code)
print(response.json())

Run this and you’ll see:

200
{'current_user_url': 'https://api.github.com/user', ...}

What just happened:

  1. requests.get() made an HTTP GET request to GitHub’s API
  2. response.status_code shows 200 (success)
  3. response.json() parsed the JSON response into a Python dictionary

This is the foundation. Everything else builds on this.

3. Understanding the Response Object

Every request returns a response object. Understanding this object is critical.

Status Codes – What They Mean

import requests

response = requests.get('https://api.github.com')

# Check if request succeeded
if response.status_code == 200:
    print("Success!")
elif response.status_code == 404:
    print("Not found")
elif response.status_code == 500:
    print("Server error")

Status codes you’ll see constantly:

  • 200 – Success, everything worked
  • 201 – Created, new resource was created
  • 400 – Bad request, you sent invalid data
  • 401 – Unauthorized, you need to authenticate
  • 403 – Forbidden, you’re authenticated but don’t have permission
  • 404 – Not found, the resource doesn’t exist
  • 500 – Server error, something broke on their end

Getting Response Data

response = requests.get('https://api.github.com')

# Get response as JSON (most common)
data = response.json()
print(data)

# Get response as text
text = response.text
print(text)

# Get raw bytes
raw = response.content
print(raw)

99% of the time you’ll use .json() because REST APIs return JSON.

Checking Response Success

response = requests.get('https://api.github.com')

# Method 1: Check status code
if response.status_code == 200:
    print("Success")

# Method 2: Use ok property
if response.ok:
    print("Success")

# Method 3: Raise exception on error (recommended)
try:
    response.raise_for_status()
    data = response.json()
    print(data)
except requests.exceptions.HTTPError as e:
    print(f"Error: {e}")

The third method is best for production code. It automatically raises an exception if something goes wrong.

4. URL Parameters: Filtering and Searching

URL parameters let you filter, search, and customize your API requests.

Basic Parameters

import requests

# Without parameters
response = requests.get('https://api.github.com/users/torvalds/repos')

# With parameters - manual URL building (don't do this)
response = requests.get('https://api.github.com/users/torvalds/repos?per_page=5&sort=updated')

# With parameters - the right way
params = {
    'per_page': 5,
    'sort': 'updated'
}
response = requests.get('https://api.github.com/users/torvalds/repos', params=params)

Why use the params argument:

  • Automatically handles URL encoding
  • Cleaner code
  • Easier to modify
  • Prevents errors with special characters

Multiple Parameters Example

# Get WordPress posts with filtering
params = {
    'per_page': 10,    # Number of results
    'page': 1,         # Page number
    'status': 'publish',  # Only published posts
    'orderby': 'date',    # Sort by date
    'order': 'desc'       # Descending order
}

response = requests.get(
    'http://localhost:8080/wp-json/wp/v2/posts',
    params=params
)

posts = response.json()
for post in posts:
    print(post['title']['rendered'])

Real scenario: When monitoring our WordPress deployment logs, we filter posts by date range and status to only get production deployment announcements from the last week.

5. Request Headers: Authentication and Metadata

Headers provide metadata about your request. The most common use is authentication.

Understanding Headers

Headers are key-value pairs sent with every request:

import requests

headers = {
    'User-Agent': 'My-Script/1.0',
    'Accept': 'application/json',
    'Content-Type': 'application/json'
}

response = requests.get(
    'https://api.github.com',
    headers=headers
)

Common headers you’ll use:

  • User-Agent – Identifies your application
  • Accept – What format you want back (usually JSON)
  • Content-Type – Format of data you’re sending
  • Authorization – Authentication credentials

Basic Authentication

from requests.auth import HTTPBasicAuth

# Method 1: Using auth parameter (recommended)
response = requests.get(
    'http://localhost:8080/wp-json/wp/v2/posts',
    auth=HTTPBasicAuth('username', 'password')
)

# Method 2: Using headers manually
import base64

credentials = base64.b64encode(b'username:password').decode('utf-8')
headers = {
    'Authorization': f'Basic {credentials}'
}
response = requests.get(
    'http://localhost:8080/wp-json/wp/v2/posts',
    headers=headers
)

Use Method 1. The requests library handles encoding for you.

Bearer Token Authentication

Many APIs use bearer tokens:

headers = {
    'Authorization': 'Bearer your_token_here'
}

response = requests.get(
    'https://api.example.com/data',
    headers=headers
)

API Key Authentication

Some APIs use API keys in headers:

headers = {
    'X-API-Key': 'your_api_key_here'
}

response = requests.get(
    'https://api.example.com/data',
    headers=headers
)

Real scenario: Our WordPress automation uses Basic Auth with application passwords. AWS APIs use signing with access keys. GitHub uses bearer tokens. Learn the pattern, and you can work with any API.

6. Sending Data with POST Requests

POST requests create new resources. You send data in the request body.

Sending JSON Data

import requests

# Data to send
data = {
    'title': 'My New Post',
    'content': 'This is the post content',
    'status': 'draft'
}

# Method 1: Using json parameter (recommended)
response = requests.post(
    'http://localhost:8080/wp-json/wp/v2/posts',
    json=data,
    auth=HTTPBasicAuth('username', 'password')
)

# Method 2: Manual JSON encoding
import json

response = requests.post(
    'http://localhost:8080/wp-json/wp/v2/posts',
    data=json.dumps(data),
    headers={'Content-Type': 'application/json'},
    auth=HTTPBasicAuth('username', 'password')
)

Use Method 1. It automatically:

  • Converts Python dict to JSON
  • Sets Content-Type header
  • Handles encoding

Sending Form Data

Some APIs expect form data instead of JSON:

data = {
    'username': 'admin',
    'password': 'secret'
}

response = requests.post(
    'https://example.com/login',
    data=data  # Note: data parameter, not json
)

The difference:

  • json=data – Sends as JSON (Content-Type: application/json)
  • data=data – Sends as form data (Content-Type: application/x-www-form-urlencoded)

Checking Response After POST

response = requests.post(
    'http://localhost:8080/wp-json/wp/v2/posts',
    json={'title': 'New Post', 'content': 'Hello'},
    auth=HTTPBasicAuth('username', 'password')
)

# Check if created successfully
if response.status_code == 201:  # 201 = Created
    created_post = response.json()
    print(f"Created post with ID: {created_post['id']}")
    print(f"URL: {created_post['link']}")
else:
    print(f"Error: {response.status_code}")
    print(response.text)

Status code 201 means “Created successfully”. Always check this after POST requests.

7. Updating Data with PUT/PATCH Requests

PUT and PATCH update existing resources.

The Difference

PUT – Replace entire resource (send all fields) PATCH – Update specific fields (send only changed fields)

In practice, many APIs treat them the same.

Updating a Resource

# Update specific fields
data = {
    'title': 'Updated Title',
    'status': 'publish'
}

response = requests.post(  # WordPress uses POST for updates
    'http://localhost:8080/wp-json/wp/v2/posts/123',
    json=data,
    auth=HTTPBasicAuth('username', 'password')
)

if response.status_code == 200:
    updated_post = response.json()
    print(f"Updated: {updated_post['title']['rendered']}")

Note: WordPress REST API uses POST for updates, but many other APIs use PUT or PATCH. Check the API documentation.

Standard PUT Request

# Most APIs use PUT for updates
response = requests.put(
    'https://api.example.com/posts/123',
    json={'title': 'New Title'},
    headers={'Authorization': 'Bearer token'}
)

8. Deleting Data with DELETE Requests

DELETE requests remove resources.

Basic Delete

response = requests.delete(
    'http://localhost:8080/wp-json/wp/v2/posts/123',
    auth=HTTPBasicAuth('username', 'password')
)

if response.status_code == 200:
    print("Post deleted successfully")

Delete with Parameters

Some APIs let you specify delete behavior:

# WordPress: force=true permanently deletes, false moves to trash
params = {
    'force': True  # Permanent deletion
}

response = requests.delete(
    'http://localhost:8080/wp-json/wp/v2/posts/123',
    params=params,
    auth=HTTPBasicAuth('username', 'password')
)

Safe Delete Pattern

Always confirm before deleting:

def delete_post(post_id):
    # First, get the post to confirm it exists
    response = requests.get(
        f'http://localhost:8080/wp-json/wp/v2/posts/{post_id}',
        auth=HTTPBasicAuth('username', 'password')
    )
    
    if response.status_code == 404:
        print(f"Post {post_id} not found")
        return False
    
    post = response.json()
    print(f"About to delete: {post['title']['rendered']}")
    
    # Now delete
    response = requests.delete(
        f'http://localhost:8080/wp-json/wp/v2/posts/{post_id}',
        auth=HTTPBasicAuth('username', 'password')
    )
    
    return response.status_code == 200

Production tip: Always log what you’re deleting. I’ve seen scripts accidentally delete production data because of a wrong variable value.

9. Error Handling: Don’t Let Your Scripts Crash

Good scripts handle errors gracefully.

Basic Error Handling

import requests

try:
    response = requests.get(
        'http://localhost:8080/wp-json/wp/v2/posts',
        timeout=10  # Always set timeout!
    )
    response.raise_for_status()  # Raise exception for bad status
    posts = response.json()
    print(f"Got {len(posts)} posts")
    
except requests.exceptions.Timeout:
    print("Request timed out - server too slow")
    
except requests.exceptions.ConnectionError:
    print("Couldn't connect to server - is it running?")
    
except requests.exceptions.HTTPError as e:
    print(f"HTTP error: {e}")
    
except requests.exceptions.RequestException as e:
    print(f"Something went wrong: {e}")

Handling Different Status Codes

response = requests.get('http://localhost:8080/wp-json/wp/v2/posts/999')

if response.status_code == 200:
    post = response.json()
    print(post)
    
elif response.status_code == 404:
    print("Post not found")
    
elif response.status_code == 401:
    print("Authentication failed - check credentials")
    
elif response.status_code == 403:
    print("Forbidden - you don't have permission")
    
elif response.status_code >= 500:
    print("Server error - try again later")
    
else:
    print(f"Unexpected status: {response.status_code}")
    print(response.text)

Retry Logic for Production

import time
import requests

def get_with_retry(url, max_retries=3):
    for attempt in range(max_retries):
        try:
            response = requests.get(url, timeout=10)
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.RequestException as e:
            print(f"Attempt {attempt + 1} failed: {e}")
            
            if attempt < max_retries - 1:
                wait_time = 2 ** attempt  # Exponential backoff: 1s, 2s, 4s
                print(f"Retrying in {wait_time} seconds...")
                time.sleep(wait_time)
            else:
                print("All retries failed")
                raise

Real scenario: Our production scripts retry failed API calls with exponential backoff. Network hiccups happen. Retry logic means temporary issues don’t cause incidents.

10. Real-World Project: WordPress Post Manager

Now let’s build something real. We’ll create a complete WordPress post manager that demonstrates all these concepts.

Project Setup

First, clone the project:

git clone https://github.com/yourusername/wordpress-docker-api
cd wordpress-docker-api

Start WordPress with Docker:

docker-compose up -d

This starts:

  • WordPress on http://localhost:8080
  • MySQL database
  • All configured and ready to use

Generate WordPress Application Password

  1. Open http://localhost:8080/wp-admin
  2. Complete WordPress installation
  3. Login with your credentials
  4. Go to Users → Profile
  5. Scroll to “Application Passwords”
  6. Enter name: “Python Script”
  7. Click “Add New Application Password”
  8. Copy the generated password

This password is for API access only. It’s different from your login password.

The Complete WordPress Manager Script

Let’s build the script piece by piece.

Step 1: Configuration and Setup

import requests
from requests.auth import HTTPBasicAuth

# Configuration
WP_URL = "http://localhost:8080"
WP_USER = "admin"
WP_APP_PASSWORD = "your_app_password_here"  # From step above
API_BASE = f"{WP_URL}/wp-json/wp/v2"

def get_auth():
    """Get authentication object for API requests"""
    return HTTPBasicAuth(WP_USER, WP_APP_PASSWORD)

Step 2: List All Posts

def list_posts(per_page=10, status='publish'):
    """List all WordPress posts"""
    try:
        endpoint = f"{API_BASE}/posts"
        
        params = {
            'per_page': per_page,
            'status': status
        }
        
        response = requests.get(
            endpoint,
            params=params,
            auth=get_auth(),
            timeout=10
        )
        
        response.raise_for_status()
        posts = response.json()
        
        print(f"\nFound {len(posts)} posts:")
        for post in posts:
            print(f"- {post['title']['rendered']} (ID: {post['id']})")
        
        return posts
        
    except requests.exceptions.RequestException as e:
        print(f"Error listing posts: {e}")
        return None

What’s happening here:

  • params dictionary becomes ?per_page=10&status=publish in the URL
  • auth=get_auth() adds Basic Authentication header
  • timeout=10 prevents hanging forever
  • raise_for_status() raises exception if status code indicates error
  • response.json() parses JSON into Python list

Step 3: Get Single Post

def get_post(post_id):
    """Get a specific post by ID"""
    try:
        endpoint = f"{API_BASE}/posts/{post_id}"
        
        response = requests.get(
            endpoint,
            auth=get_auth(),
            timeout=10
        )
        
        if response.status_code == 404:
            print(f"Post {post_id} not found")
            return None
        
        response.raise_for_status()
        post = response.json()
        
        print(f"\nTitle: {post['title']['rendered']}")
        print(f"Status: {post['status']}")
        print(f"Date: {post['date']}")
        print(f"URL: {post['link']}")
        
        return post
        
    except requests.exceptions.RequestException as e:
        print(f"Error getting post: {e}")
        return None

Key difference from listing: we check for 404 specifically. A missing post isn’t an error we want to retry.

Step 4: Create New Post

def create_post(title, content, status='draft'):
    """Create a new WordPress post"""
    try:
        endpoint = f"{API_BASE}/posts"
        
        data = {
            'title': title,
            'content': content,
            'status': status
        }
        
        response = requests.post(
            endpoint,
            json=data,  # Automatically converts to JSON
            auth=get_auth(),
            timeout=10
        )
        
        response.raise_for_status()
        post = response.json()
        
        print(f"\nPost created successfully!")
        print(f"ID: {post['id']}")
        print(f"Title: {post['title']['rendered']}")
        print(f"URL: {post['link']}")
        
        return post
        
    except requests.exceptions.RequestException as e:
        print(f"Error creating post: {e}")
        if hasattr(e, 'response') and e.response is not None:
            print(f"Response: {e.response.text}")
        return None

The json=data parameter:

  • Converts Python dict to JSON
  • Sets Content-Type: application/json header
  • Handles character encoding

Step 5: Update Existing Post

def update_post(post_id, title=None, content=None, status=None):
    """Update an existing WordPress post"""
    try:
        endpoint = f"{API_BASE}/posts/{post_id}"
        
        # Build data with only provided fields
        data = {}
        if title is not None:
            data['title'] = title
        if content is not None:
            data['content'] = content
        if status is not None:
            data['status'] = status
        
        if not data:
            print("No fields to update")
            return None
        
        response = requests.post(  # WordPress uses POST for updates
            endpoint,
            json=data,
            auth=get_auth(),
            timeout=10
        )
        
        response.raise_for_status()
        post = response.json()
        
        print(f"\nPost {post_id} updated successfully!")
        print(f"New title: {post['title']['rendered']}")
        
        return post
        
    except requests.exceptions.RequestException as e:
        print(f"Error updating post: {e}")
        return None

We only send fields that changed. This is more efficient and prevents accidentally overwriting fields.

Step 6: Delete Post

def delete_post(post_id, force=False):
    """Delete a WordPress post"""
    try:
        endpoint = f"{API_BASE}/posts/{post_id}"
        
        params = {
            'force': force  # True = permanent, False = trash
        }
        
        response = requests.delete(
            endpoint,
            params=params,
            auth=get_auth(),
            timeout=10
        )
        
        response.raise_for_status()
        
        if force:
            print(f"\nPost {post_id} permanently deleted")
        else:
            print(f"\nPost {post_id} moved to trash")
        
        return True
        
    except requests.exceptions.RequestException as e:
        print(f"Error deleting post: {e}")
        return False

The force parameter controls deletion behavior. Default is move to trash (safer).

Step 7: Main Automation Script

def main():
    """Example automation workflow"""
    print("=" * 50)
    print("WordPress Deployment Automation")
    print("=" * 50)
    
    # Get current deployment info
    import datetime
    deployment_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
    
    # Create deployment changelog post
    post_content = f"""
    <h2>Deployment Details</h2>
    <ul>
        <li><strong>Date:</strong> {deployment_date}</li>
        <li><strong>Version:</strong> v2.3.1</li>
        <li><strong>Environment:</strong> Production</li>
    </ul>
    
    <h2>Changes</h2>
    <ul>
        <li>Fixed database connection timeout</li>
        <li>Updated security patches</li>
        <li>Improved API performance</li>
    </ul>
    """
    
    # Create the post
    new_post = create_post(
        title=f"Production Deployment - {deployment_date}",
        content=post_content,
        status="publish"
    )
    
    if new_post:
        print(f"\nDeployment logged to WordPress: {new_post['link']}")
        
        # Update the post with additional info (example)
        update_post(
            post_id=new_post['id'],
            content=post_content + "\n<p><em>Deployment completed successfully</em></p>"
        )
    
    # List recent deployment posts
    print("\n" + "=" * 50)
    print("Recent Deployments:")
    print("=" * 50)
    list_posts(per_page=5)

if __name__ == "__main__":
    main()

This is a real automation workflow. Every production deployment creates a WordPress post automatically.

Running the Complete Project

# Install dependencies
pip install requests

# Update credentials in the script
# WP_USER = "your_username"
# WP_APP_PASSWORD = "your_app_password"

# Run the script
python wordpress_manager.py

Output:

==================================================
WordPress Deployment Automation
==================================================

Post created successfully!
ID: 42
Title: Production Deployment - 2025-01-15 14:30
URL: http://localhost:8080/production-deployment-2025-01-15-1430/

Post 42 updated successfully!
New title: Production Deployment - 2025-01-15 14:30

==================================================
Recent Deployments:
==================================================

Found 5 posts:
- Production Deployment - 2025-01-15 14:30 (ID: 42)
- Production Deployment - 2025-01-14 09:15 (ID: 41)
- Production Deployment - 2025-01-13 16:45 (ID: 40)
- Production Deployment - 2025-01-12 11:20 (ID: 39)
- Production Deployment - 2025-01-11 13:00 (ID: 38)

Common Patterns You’ll Use Everywhere

These patterns work with any REST API, not just WordPress.

Pattern 1: Pagination

def get_all_posts():
    """Get all posts across multiple pages"""
    all_posts = []
    page = 1
    
    while True:
        params = {
            'per_page': 100,
            'page': page
        }
        
        response = requests.get(
            f"{API_BASE}/posts",
            params=params,
            auth=get_auth()
        )
        
        if response.status_code != 200:
            break
        
        posts = response.json()
        if not posts:  # No more posts
            break
        
        all_posts.extend(posts)
        page += 1
    
    return all_posts

Pattern 2: Rate Limiting

import time

def api_call_with_rate_limit(url, delay=1):
    """Make API call with rate limiting"""
    response = requests.get(url, auth=get_auth())
    time.sleep(delay)  # Wait before next call
    return response

Pattern 3: Batch Operations

def create_multiple_posts(posts_data):
    """Create multiple posts with progress tracking"""
    created = []
    
    for i, post_data in enumerate(posts_data, 1):
        print(f"Creating post {i}/{len(posts_data)}...")
        
        post = create_post(
            title=post_data['title'],
            content=post_data['content'],
            status=post_data.get('status', 'draft')
        )
        
        if post:
            created.append(post)
        
        time.sleep(0.5)  # Rate limiting
    
    print(f"\nCreated {len(created)} posts successfully")
    return created

Pattern 4: Session Reuse

# Create session for connection pooling
session = requests.Session()
session.auth = get_auth()
session.headers.update({'User-Agent': 'WordPress-Manager/1.0'})

# Reuse session for multiple requests (faster)
response1 = session.get(f"{API_BASE}/posts")
response2 = session.get(f"{API_BASE}/posts/1")
response3 = session.get(f"{API_BASE}/posts/2")

# Close session when done
session.close()

Best Practices That Will Save Your Career

Always Set Timeouts

# Bad - can hang forever
response = requests.get(url)

# Good - fails after 10 seconds
response = requests.get(url, timeout=10)

# Better - separate connect and read timeouts
response = requests.get(url, timeout=(3, 10))  # 3s connect, 10s read

I’ve seen production scripts hang for hours because they didn’t set timeouts.

Use Environment Variables for Credentials

import os

# Bad - hardcoded credentials
WP_USER = "admin"
WP_PASSWORD = "secret123"

# Good - environment variables
WP_USER = os.environ.get('WP_USER')
WP_PASSWORD = os.environ.get('WP_PASSWORD')

if not WP_USER or not WP_PASSWORD:
    raise ValueError("Missing credentials in environment")

Never commit credentials to Git.

Log Everything Important

import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def create_post(title, content):
    logger.info(f"Creating post: {title}")
    
    try:
        response = requests.post(
            f"{API_BASE}/posts",
            json={'title': title, 'content': content},
            auth=get_auth()
        )
        response.raise_for_status()
        
        post = response.json()
        logger.info(f"Created post ID: {post['id']}")
        return post
        
    except requests.exceptions.RequestException as e:
        logger.error(f"Failed to create post: {e}")
        return None

Logs save you when debugging production issues at 3 AM.

Handle Different Response Formats

def safe_json_parse(response):
    """Safely parse JSON response"""
    try:
        return response.json()
    except ValueError:
        print(f"Invalid JSON response: {response.text}")
        return None

Not all APIs return valid JSON on errors.

Validate Input Before Sending

def create_post(title, content, status='draft'):
    """Create post with validation"""
    # Validate inputs
    if not title or not title.strip():
        raise ValueError("Title cannot be empty")
    
    if not content or not content.strip():
        raise ValueError("Content cannot be empty")
    
    if status not in ['draft', 'publish', 'private']:
        raise ValueError(f"Invalid status: {status}")
    
    # Make request
    response = requests.post(
        f"{API_BASE}/posts",
        json={'title': title, 'content': content, 'status': status},
        auth=get_auth()
    )
    
    return response.json()

Catch errors early, before making API calls.

What’s Next?

Master these Python requests fundamentals, and you can integrate with any REST API. But here’s what most people get wrong: they stop here.

The real power comes from combining this with your DevOps workflows:

  • Automate WordPress posts from CI/CD pipelines
  • Monitor API health in production
  • Sync data between systems
  • Build custom dashboards
  • Create deployment automation

This WordPress project is on GitHub. Clone it, run it, break it, fix it. That’s how you learn.

Coming up next:

  • Building REST APIs with Flask
  • Automating AWS with boto3
  • Creating Kubernetes operators with Python
  • Advanced authentication patterns

Remember: Every cloud tool you use is just making REST API calls under the hood. Understanding requests means understanding how everything connects.

Connect with me:

  • LinkedIn: https://www.linkedin.com/in/akhilesh-mishra-0ab886124/
  • Twitter: https://x.com/livingdevops
  • GitHub: [Your GitHub URL]

Thanks for reading, see you at the next one.

If you found this article useful, do clap, comment, share, follow, and subscribe.

Share
Akhilesh Mishra

Akhilesh Mishra

I am Akhilesh Mishra, a self-taught Devops engineer with 11+ years working on private and public cloud (GCP & AWS)technologies.

I also mentor DevOps aspirants in their journey to devops by providing guided learning and Mentorship.

Topmate: https://topmate.io/akhilesh_mishra/