API Auth, Headers & Error Handling
In modern API architecture, communication parameters are split across two channels:
| Channel | Location | Primary Purpose | Example |
|---|---|---|---|
| Query Parameters | Appended to the URL path after ? | Filtering, sorting, search keywords, and pagination | /products?category=laptops&sort=price_asc&page=2 |
| HTTP Headers | Metadata transmission alongside the request | Authentication tokens, content formats, and client identification | Authorization: Bearer eyJhbGci...User-Agent: MyApp/2.0 |
The 3 Standard API Authentication Patterns:
- Bearer Tokens (JWT / OAuth2): Standardized header format used by modern web and cloud APIs:
headers = {"Authorization": f"Bearer {jwt_access_token}"} - Custom Header API Keys: Used by enterprise SaaS APIs (like OpenAI, Stripe, and AWS API Gateway):
headers = {"X-API-Key": "sk_live_9988776655"} - HTTP Basic Authentication: Base64 encodes
username:passwordinto the Authorization header (requests.get(url, auth=('admin', 'secret'))).
import os
def build_secure_headers(api_key: str, client_name: str = "OurCompiler-Client/3.0"):
"""Constructs production-standard authenticated headers."""
return {
"Authorization": f"Bearer {api_key}",
"User-Agent": client_name,
"Accept": "application/json",
"Content-Type": "application/json",
"X-Request-Timestamp": "2026-08-14T10:00:00Z"
}
# Build and inspect header dictionary:
sample_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.demo_token_payload"
headers = build_secure_headers(sample_token)
print("--- ๐ก๏ธ Production Request Headers ---")
for key, value in headers.items():
masked_value = value[:22] + "..." if key == "Authorization" else value
print(f"โข {key:20}: {masked_value}")
Never log full Authorization tokens in server log files. Always mask sensitive tokens (e.g. eyJhbGci...) to prevent credential leaks in monitoring dashboards.
In production microservices, failing to set a timeout is a critical mistake. If a remote API server hangs, your Python worker thread will block indefinitely waiting for a TCP packet that never arrives, exhausting your server's thread pool.
The 3 Pillars of Resilient API Consumption:
- Explicit Timeouts: Always pass a timeout tuple:
timeout=(connect_timeout, read_timeout)(e.g.timeout=(3.05, 10.0)). - Automated Error Trapping (
raise_for_status()): Converts HTTP 4xx (Client Errors) and 5xx (Server Errors) into catchable Python exceptions. - Session Connection Pooling (
requests.Session()): Reuses underlying TCP connections (HTTP Keep-Alive), speeding up consecutive API requests by 300-400%.
import time
class ResilientAPIClient:
"""Production-grade resilient API client with timeout and error handling."""
def __init__(self, base_url="https://api.github.com"):
self.base_url = base_url
def execute_api_call(self, endpoint, simulate_condition="success"):
"""Simulates network request with robust error trapping."""
print(f"\n--- ๐ก Calling Endpoint: {endpoint} (Condition: {simulate_condition}) ---")
try:
if simulate_condition == "timeout":
# Simulates TCP read timeout:
raise TimeoutError("Connection timed out after 3.0 seconds")
elif simulate_condition == "404":
raise Exception("HTTPError: 404 Client Error: Resource Not Found")
elif simulate_condition == "500":
raise Exception("HTTPError: 500 Server Error: Internal Server Crash")
elif simulate_condition == "connection_refused":
raise ConnectionRefusedError("Failed to establish TCP connection (DNS failure)")
# Successful response:
return {"status": "success", "data": {"server_time": "2026-08-14 10:30:00", "ping": "pong"}}
except TimeoutError as timeout_err:
print(f"โณ [TIMEOUT TRAPPED] Remote server too slow to respond: {timeout_err}")
return None
except ConnectionRefusedError as conn_err:
print(f"๐ [NETWORK TRAPPED] Target host unreachable: {conn_err}")
return None
except Exception as http_err:
print(f"โ [HTTP ERROR TRAPPED] {http_err}")
return None
# Test all failure scenarios:
client = ResilientAPIClient()
client.execute_api_call("/status", "success")
client.execute_api_call("/users/999", "404")
client.execute_api_call("/checkout", "500")
client.execute_api_call("/slow-query", "timeout")
Every HTTPS request requires a DNS lookup, TCP 3-way handshake, and TLS/SSL certificate negotiation (~100ms total). requests.Session() keeps the socket open, reducing subsequent request overhead to ~5ms!
Catching generic exceptions hides programming bugs (like NameError or Typo bugs). In production, catch specific exceptions from requests: requests.exceptions.Timeout, requests.exceptions.ConnectionError, and requests.exceptions.HTTPError.
Build a function safe_fetch_user(user_id) that returns a fallback dictionary {"name": "Guest", "is_fallback": True} if any network or HTTP error occurs.
def safe_fetch_user_simulation(user_id, simulate_fail=False):
try:
if simulate_fail:
raise ConnectionError("Remote server offline")
return {"user_id": user_id, "name": "Balaji", "is_fallback": False}
except Exception as err:
print(f"โ ๏ธ Warning: Could not fetch user #{user_id} ({err}). Returning fallback profile.")
return {"user_id": user_id, "name": "Guest User", "is_fallback": True}
print("Normal Fetch: ", safe_fetch_user_simulation(101, False))
print("Fallback Fetch:", safe_fetch_user_simulation(101, True))
Q What is Exponential Backoff in API clients?
Exponential Backoff is an algorithm that retries failed requests with exponentially increasing delays (e.g. wait 1s, then 2s, then 4s, then 8s) to avoid overwhelming a recovering server with simultaneous retries.
Q What is the purpose of the Accept header in HTTP requests?
The "Accept: application/json" header informs the server of the data format the client expects to receive in return (Content Negotiation).
Q How do I pass query parameters with identical keys in requests?
Pass a list of tuples or a dictionary with list values: requests.get(url, params={"tag": ["python", "fastapi"]}) which compiles to "?tag=python&tag=fastapi".