USPS v3 API with Python:
Complete Quickstart Guide
The usps-v3 Python SDK provides a clean, typed interface to the USPS v3 REST API. This tutorial walks through installation, authentication, and the four most common operations: address validation, package tracking, rate shopping, and error handling.
Prerequisites
- USPS Developer Portal account — Register at
developer.usps.comand create an application to get your Client ID and Client Secret. - Python 3.8+ — The SDK uses modern type hints and
dataclasses. - One dependency —
httpx(installed automatically with the SDK).
# Requires Python 3.8+
pip install usps-v3 Authentication
The USPS v3 API uses OAuth 2.0 client credentials grant. Tokens last 8 hours. The SDK handles the entire flow automatically — you never touch a token directly.
from usps_v3 import USPSClient
# Client ID and Client Secret from USPS Developer Portal
# https://developer.usps.com/apis
client = USPSClient(
client_id="your_client_id",
client_secret="your_client_secret",
)
# That's it. OAuth tokens are managed automatically:
# - Token fetched on first API call
# - Cached in memory (8-hour lifetime)
# - Refreshed 30 minutes before expiry
# - Thread-safe for concurrent usage Testing environment: Pass environment="testing" to the client constructor to use the USPS sandbox at apis-tem.usps.com. Same credentials, no real shipments.
Address Validation
Address validation is the most common API call. It standardizes street addresses, confirms deliverability via DPV (Delivery Point Validation), and flags vacant addresses.
from usps_v3 import USPSClient
client = USPSClient(
client_id="your_client_id",
client_secret="your_client_secret",
)
# Validate a US address
result = client.addresses.validate(
street_address="1600 Pennsylvania Ave NW",
city="Washington",
state="DC",
zip_code="20500",
)
# Access standardized fields
print(result.address.street_address) # 1600 PENNSYLVANIA AVE NW
print(result.address.city) # WASHINGTON
print(result.address.state) # DC
print(result.address.zip_code) # 20500
print(result.address.zip_plus_4) # 0005
# Delivery Point Validation
print(result.address.dpv_confirmation) # Y
print(result.address.vacant) # N | DPV Code | Meaning | Action |
|---|---|---|
| Y | Confirmed deliverable | Accept the address |
| S | Secondary info missing (apt/suite) | Prompt user for unit number |
| D | Secondary confirmed but not matched | Verify unit number with user |
| N | Not deliverable | Reject or flag for manual review |
Package Tracking
Track any USPS package by tracking number. The response includes the current status, delivery date, and a full event history with timestamps and locations.
# Track a package by tracking number
tracking = client.tracking.get(
tracking_number="9400111899223456789012",
)
# Latest status
print(tracking.status) # "Delivered"
print(tracking.status_category) # "Delivered"
print(tracking.delivery_date) # "2026-03-08"
# Full event history (most recent first)
for event in tracking.events:
print(
f"{event.date} {event.time}",
f"| {event.city}, {event.state}",
f"| {event.description}",
)
# Output:
# 2026-03-08 10:15 | Washington, DC | Delivered
# 2026-03-08 06:30 | Washington, DC | Out for Delivery
# 2026-03-07 22:10 | Washington, DC | Arrived at Hub Tip: Tracking data is real-time but changes frequently. Don't cache tracking responses — poll at reasonable intervals (every 30-60 minutes) or use USPS webhook notifications for production systems.
Rate Shopping
Compare shipping prices across all available USPS services for a given package. The API returns Priority Mail, Priority Mail Express, Ground Advantage, and any other eligible services with prices and estimated delivery times.
# Compare domestic shipping prices
rates = client.prices.search(
origin_zip="10001", # New York, NY
destination_zip="90210", # Beverly Hills, CA
weight=2.5, # pounds
length=12, # inches
width=8,
height=6,
)
# Iterate over available services
for rate in rates.rates:
print(
f"{rate.mail_class:<25}",
f"${rate.total_price:>6.2f}",
f"| {rate.delivery_days} day(s)",
)
# Output:
# Priority Mail Express $32.75 | 1 day(s)
# Priority Mail $12.10 | 2 day(s)
# USPS Ground Advantage $8.45 | 5 day(s)
# Get the cheapest option
cheapest = min(rates.rates, key=lambda r: r.total_price)
print(f"Best deal: {cheapest.mail_class} at ${cheapest.total_price:.2f}") Commercial pricing: If you have a USPS commercial account, the SDK automatically returns commercial rates (lower than retail). Set rate_indicator="CP" to explicitly request commercial plus pricing.
Error Handling
The SDK raises typed exceptions for every error category. The most important one to handle is USPSRateLimitError — USPS caps direct access at 60 requests/hour.
from usps_v3 import USPSClient
from usps_v3.exceptions import (
USPSError,
USPSRateLimitError,
USPSAuthError,
USPSValidationError,
)
import time
client = USPSClient(
client_id="your_client_id",
client_secret="your_client_secret",
)
def validate_with_retry(street, city, state, max_retries=3):
for attempt in range(max_retries):
try:
return client.addresses.validate(
street_address=street,
city=city,
state=state,
)
except USPSRateLimitError as e:
# 429 — back off exponentially
wait = 2 ** attempt
print(f"Rate limited. Retrying in {wait}s...")
time.sleep(wait)
except USPSAuthError:
# 401 — token expired or bad credentials
raise
except USPSValidationError as e:
# Bad input (missing required field, etc.)
print(f"Validation error: {e.message}")
raise
except USPSError as e:
# Catch-all for any USPS API error
print(f"USPS error {e.status_code}: {e.message}")
raise
raise USPSRateLimitError("Max retries exceeded") | Exception | HTTP Status | Cause |
|---|---|---|
| USPSAuthError | 401 | Bad credentials or expired token |
| USPSValidationError | 400 | Missing or invalid request parameters |
| USPSRateLimitError | 429 | Exceeded 60 req/hr limit |
| USPSError | 5xx | USPS server error (retry safe) |
Next Steps
You've got the basics. Here's where to go from here:
- Full API Documentation — Complete endpoint reference with request/response schemas for all 37 routes.
- Migration Guide — Moving from USPS Web Tools XML? Complete endpoint mapping and OAuth setup walkthrough.
- Rate Limit Strategies — Caching, queuing, and architecture patterns for production workloads beyond 60 req/hr.
- Node.js SDK — Prefer JavaScript? The Node.js SDK has the same API surface with full TypeScript types.
- Source Code — MIT licensed. Contributions welcome.
Need higher rate limits?
RevAddress provides a managed USPS v3 API with 600 req/min, built-in caching, automatic token management, and BYOK support. Drop in the SDK and scale.