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 USPS's testing base URL at apis-tem.usps.com before switching to production.
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 $XX.XX | 1 day(s)
# Priority Mail $XX.XX | 2 day(s)
# USPS Ground Advantage $X.XX | 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 — Full endpoint reference with request/response schemas, BYOK notes, and admin diagnostics.
- 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 USPS v3 developer tooling with built-in caching, automatic token management, and BYOK credential isolation. Drop in the SDK and scale.
Related Articles
Using Node.js instead? Node.js Quickstart
Full TypeScript support with automatic OAuth token management.
Using PHP instead? PHP Quickstart
Zero deps, PHP 8.1+. Magento carrier module, Laravel service provider, WooCommerce integration.
usps-api Is Dead — Here's the Replacement
The old PyPI packages are permanently broken. One-command migration to usps-v3.