Making HTTP Requests with httpx
The httpx library is a modern HTTP client for Python that provides both synchronous and asynchronous APIs. It was designed as a drop-in replacement for requests with additional features like HTTP/2 support, async capabilities, and connection pooling.
If you already know requests, httpx will feel familiar. If you are new to HTTP clients, httpx is a great place to start because of its clean API and excellent documentation.
Installing httpx
Install httpx via pip. The standard package includes both sync and async support:
pip install httpx
For HTTP/2 support (recommended for modern APIs), install with the http2 extra:
pip install httpx[http2]
Your First Request
The basic API mirrors requests. Make a GET request and inspect the response:
import httpx
response = httpx.get("https://api.github.com/users/octocat")
print(response.status_code) # 200
print(response.text) # Raw response body
print(response.headers) # Response headers dict
The response object behaves much like requests. You get status codes, headers, and body content with the same intuitive interface.
Working with JSON Responses
Like requests, httpx automatically decodes JSON responses:
import httpx
response = httpx.get("https://api.github.com/users/octocat")
data = response.json()
print(data["login"]) # octocat
print(data["followers"]) # number of followers
The .json() method raises an error if the response is not valid JSON, so wrap it in try-except when dealing with uncertain responses.
Sending POST Requests
Send data to create or update resources:
import httpx
payload = {"username": "alice", "password": "secret123"}
response = httpx.post("https://httpbin.org/post", data=payload)
print(response.status_code) # 200
print(response.json()) # Echoes back what was sent
For JSON bodies, use the json= parameter—it sets the correct Content-Type automatically:
import httpx
payload = {"title": "My Post", "body": "Content here"}
response = httpx.post("https://httpbin.org/post", json=payload)
print(response.status_code) # 200
Query Parameters
Build URLs with query parameters using the params argument:
import httpx
params = {"q": "httpx python", "limit": 5}
response = httpx.get("https://httpbin.org/get", params=params)
print(response.url) # https://httpbin.org/get?q=httpx+python&limit=5
httpx handles URL encoding automatically, so special characters are escaped properly.
Custom Headers
Pass custom headers with the headers argument:
import httpx
headers = {
"Authorization": "Bearer your-token-here",
"Accept": "application/json",
"User-Agent": "MyApp/1.0"
}
response = httpx.get("https://api.example.com/data", headers=headers)
Common use cases include authentication tokens, API keys, and content negotiation.
Handling Errors
HTTP errors happen. httpx provides several ways to handle them:
import httpx
response = httpx.get("https://httpbin.org/status/404")
# Check status code manually
if response.status_code == 404:
print("Resource not found")
# Raise an exception for bad status codes
try:
response.raise_for_status()
except httpx.HTTPStatusError as e:
print(f"Request failed: {e}")
Using raise_for_status() is cleaner than checking codes manually—it raises an exception for any 4xx or 5xx response.
Timeouts
Always use timeouts to prevent hanging requests:
import httpx
# Timeout after 10 seconds
response = httpx.get("https://httpbin.org/delay/10", timeout=10.0)
# Raises httpx.TimeoutException
You can set different timeouts for connect and read operations:
import httpx
timeout = httpx.Timeout(5.0, connect=2.0)
response = httpx.get("https://api.example.com/data", timeout=timeout)
A timeout of 5-10 seconds is reasonable for most APIs.
Client Objects
For multiple requests, create a Client to reuse connections:
import httpx
client = httpx.Client()
client.headers.update({"Authorization": "Bearer token"})
# Reuses connection and headers
response1 = client.get("https://api.example.com/users")
response2 = client.get("https://api.example.com/posts")
client.close() # Always close when done
Using a Client provides connection pooling, which significantly improves performance when making multiple requests to the same host.
Async Support
httpx shines when you need async HTTP requests. Use AsyncClient for non-blocking requests:
import httpx
import asyncio
async def fetch_data():
async with httpx.AsyncClient() as client:
response = await client.get("https://api.github.com/users/octocat")
return response.json()
data = asyncio.run(fetch_data())
print(data["login"]) # octocat
The async API mirrors the sync API but uses await. Always use async with to ensure proper cleanup.
Combine multiple async requests for parallel fetching:
import httpx
import asyncio
async def fetch_multiple():
async with httpx.AsyncClient() as client:
tasks = [
client.get("https://api.github.com/users/octocat"),
client.get("https://api.github.com/users/torvalds"),
client.get("https://api.github.com/users/gvanrossum"),
]
responses = await asyncio.gather(*tasks)
return [r.json() for r in responses]
users = asyncio.run(fetch_multiple())
for user in users:
print(user["login"])
This approach dramatically reduces total wait time when fetching from multiple endpoints.
HTTP/2 Support
If you installed httpx with HTTP/2 support, enable it per-client:
import httpx
client = httpx.Client(http2=True)
response = client.get("https://example.com")
client.close()
HTTP/2 multiplexes multiple requests over a single connection, which can significantly reduce latency when making many requests to the same server.
Real-World Example: Weather API
Here is a practical async example:
import httpx
import asyncio
import os
API_KEY = os.environ.get("WEATHER_API_KEY")
CITY = "London"
async def get_weather():
url = f"https://api.openweathermap.org/data/2.5/weather"
params = {"q": CITY, "appid": API_KEY, "units": "metric"}
async with httpx.AsyncClient(timeout=10.0) as client:
try:
response = await client.get(url, params=params)
response.raise_for_status()
data = response.json()
print(f"Temperature: {data[main][temp]}C")
print(f"Conditions: {data[weather][0][description]}")
except httpx.TimeoutException:
print("Request timed out")
except httpx.HTTPStatusError as e:
print(f"API error: {e}")
asyncio.run(get_weather())
This pattern—timeout, raise_for_status(), JSON parsing, and exception handling—works well for most API integrations.
See Also
- requests-library — The classic synchronous HTTP library
- json-module — JSON encoding and decoding in Python
- asyncio-basics — Introduction to async programming in Python