0Pricing
AI Agents · Lesson

Handling API Responses and Errors

Parsing JSON responses, error codes, and exception handling patterns.

Handling API Responses and Errors is a free AI Agents lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

The Response Object

Every requests call returns a Response object. It contains everything the server sent back: the status code, headers, and body. Before processing the body, always inspect the status code — a response with a 500 status still has a body, but it won't contain the data you wanted.

import requests

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

# Key attributes of the response
print(response.status_code)       # e.g. 200
print(response.headers)           # dict of response headers
print(response.headers.get('Content-Type'))  # 'application/json'
print(response.text)              # raw response body as string
print(response.content)           # raw bytes

Parsing JSON with response.json()

Call response.json() to automatically parse the response body as JSON into a Python dict or list. This is equivalent to json.loads(response.text), but also validates that the Content-Type is appropriate.

Only call .json() when you know the response is actually JSON — check the Content-Type header first.

import requests

response = requests.get(
    'https://api.example.com/users/42',
    headers={'Authorization': 'Bearer YOUR_KEY'}
)

# Parse JSON body
user = response.json()

# Access fields safely with .get()
name = user.get('name', 'Unknown')
email = user.get('email', '')
roles = user.get('roles', [])

print(f'User: {name} ({email})')
print(f'Roles: {roles}')

Checking status_code Before Parsing

Never call response.json() without first confirming the request succeeded. Error responses (4xx/5xx) often return JSON error details — useful for debugging — but they're not the data you need. Always check status_code first.

import requests

response = requests.post(
    'https://api.example.com/tasks',
    json={'title': 'Write report'},
    headers={'Authorization': 'Bearer YOUR_KEY'}
)

if response.status_code == 201:
    task = response.json()
    print('Task created, ID:', task['id'])
elif response.status_code == 400:
    error = response.json()
    print('Validation error:', error.get('message'))
elif response.status_code == 401:
    print('Auth failed — check your token')
else:
    print(f'Unexpected status {response.status_code}: {response.text[:200]}')

raise_for_status() — Automatic Error Raising

response.raise_for_status() raises an HTTPError exception automatically if the status code is 4xx or 5xx. This is a clean way to convert bad HTTP responses into Python exceptions, letting you use try/except instead of long if/elif chains.

import requests
from requests.exceptions import HTTPError

try:
    response = requests.get(
        'https://api.example.com/users/9999',
        headers={'Authorization': 'Bearer YOUR_KEY'}
    )
    response.raise_for_status()  # raises if status >= 400
    user = response.json()
    print('Found user:', user['name'])

except HTTPError as e:
    print(f'HTTP error: {e.response.status_code}')
    print('Details:', e.response.text[:300])

Handling JSONDecodeError

Sometimes an API returns a non-JSON response when you expect JSON — a server error page in HTML, an empty body, or a binary file. Calling response.json() on these raises json.JSONDecodeError. Always catch this to avoid silent agent crashes.

import requests
import json

response = requests.get(
    'https://api.example.com/report',
    headers={'Authorization': 'Bearer YOUR_KEY'}
)

try:
    data = response.json()
except json.JSONDecodeError as e:
    print(f'Response is not valid JSON: {e}')
    print('Content-Type:', response.headers.get('Content-Type'))
    print('First 200 chars:', response.text[:200])
    # Decide: is this an HTML error page? A CSV file?
    data = None

if data is None:
    print('Falling back to text processing')

ConnectionError — Network Problems

A ConnectionError happens when your agent can't reach the server at all — DNS resolution failure, server offline, firewall blocking the request. It's a network-level failure before any HTTP even happens.

Unlike a 5xx, this isn't the server's response — the connection never happened.

import requests
from requests.exceptions import ConnectionError

try:
    response = requests.get('https://api.example.com/data')
    data = response.json()
except ConnectionError as e:
    print('Cannot reach server. Possible causes:')
    print('- DNS failure (bad hostname)')
    print('- Server is down')
    print('- No internet connection')
    print('- Firewall blocking the port')
    print(f'Error detail: {e}')
    # Consider: queue the request for retry when connectivity returns

Timeout — Preventing Stuck Agents

By default, requests waits forever for a response. A slow or hanging server will freeze your agent indefinitely. Always set a timeout: a tuple of (connect_timeout, read_timeout) in seconds. A Timeout exception is raised if the server doesn't respond in time.

import requests
from requests.exceptions import Timeout

try:
    response = requests.get(
        'https://api.example.com/slow-endpoint',
        headers={'Authorization': 'Bearer YOUR_KEY'},
        timeout=(5, 30)  # 5s to connect, 30s to read
    )
    data = response.json()
except Timeout:
    print('Request timed out after 30 seconds')
    print('Options: retry, use cached result, or alert operator')

Comprehensive Exception Handling

In production agents, catch all requests exceptions in a consistent hierarchy. requests.exceptions.RequestException is the base class for all requests errors — catching it gives you a safety net for unexpected network issues.

import requests
import json
from requests.exceptions import (
    ConnectionError, Timeout, HTTPError, RequestException
)

def safe_api_call(url, headers):
    try:
        r = requests.get(url, headers=headers, timeout=(5, 30))
        r.raise_for_status()
        return r.json()
    except Timeout:
        print('ERROR: Request timed out')
    except ConnectionError:
        print('ERROR: Cannot reach server')
    except HTTPError as e:
        print(f'ERROR: HTTP {e.response.status_code}')
        try:
            print('API error:', e.response.json().get('message'))
        except json.JSONDecodeError:
            print('Non-JSON error body')
    except RequestException as e:
        print(f'ERROR: Unexpected request error: {e}')
    return None

Logging Responses for Debugging

When an agent misbehaves, you need enough context to diagnose it. Log the request method, URL, status code, and relevant response details — but never log API keys. Use Python's built-in logging module rather than print statements for production agents.

import logging
import requests

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('agent.api')

def logged_request(method, url, **kwargs):
    logger.info(f'-> {method.upper()} {url}')
    response = requests.request(method, url, **kwargs)
    logger.info(
        f'<- {response.status_code} '
        f'({len(response.content)} bytes) '
        f'{response.elapsed.total_seconds():.2f}s'
    )
    if response.status_code >= 400:
        logger.error(f'Error body: {response.text[:500]}')
    return response

Handling Paginated Responses

Many APIs return data in pages. Your agent must follow pagination links to get all results. Look for a next URL in the response or a page/cursor field, and loop until there are no more pages.

import requests

def get_all_items(base_url, headers):
    all_items = []
    url = f'{base_url}/items?page=1&limit=100'

    while url:
        response = requests.get(url, headers=headers)
        response.raise_for_status()
        data = response.json()

        all_items.extend(data.get('items', []))

        # Follow 'next' link if present
        url = data.get('next_page_url')  # None stops the loop

        print(f'Fetched {len(all_items)} items so far...')

    print(f'Total: {len(all_items)} items')
    return all_items

Streaming Large Responses

For large responses (files, long AI outputs), use stream=True to avoid loading the entire response into memory at once. Read the response in chunks. This is essential when your agent processes large datasets or streams AI-generated text.

import requests

response = requests.get(
    'https://api.example.com/large-report',
    headers={'Authorization': 'Bearer YOUR_KEY'},
    stream=True
)

response.raise_for_status()

# Write streamed content to file
with open('report.json', 'wb') as f:
    for chunk in response.iter_content(chunk_size=8192):
        if chunk:
            f.write(chunk)

print('Download complete')

# For streaming JSON lines (NDJSON):
for line in response.iter_lines():
    if line:
        import json
        record = json.loads(line)
        print(record)

Quick Check: raise_for_status

Test your understanding of response error handling.

Response Handling Recap

Robust response handling is what separates a brittle agent from a reliable one:

  • Always check status_code before parsing the body
  • Use response.json() to parse, catch JSONDecodeError if body might not be JSON
  • Use raise_for_status() to convert HTTP errors to exceptions
  • Catch ConnectionError for network failures and Timeout for slow servers
  • Always set a timeout=(connect, read) tuple on every request
  • Log requests and responses (without keys) for debuggability

Frequently asked questions

Is the “Handling API Responses and Errors” lesson free?

Yes — the full text of “Handling API Responses and Errors” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.

What will I learn in “Handling API Responses and Errors”?

Parsing JSON responses, error codes, and exception handling patterns. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Agents?

No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Handling API Responses and Errors” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Agents lesson?

Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. REST API Fundamentals for Agent Developers
  2. Authentication: API Keys and OAuth
  3. Handling API Responses and Errors
  4. Rate Limiting and Retry Logic
← Back to AI Agents