Skip to content
· 10 min read · Tutorial

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.com and create an application to get your Client ID and Client Secret.
  • Python 3.8+ — The SDK uses modern type hints and dataclasses.
  • One dependencyhttpx (installed automatically with the SDK).
Installation
# 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.

Python — Client Setup
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.

Python — Validate an Address
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.

Python — Track a Package
# 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.

Python — Compare Shipping Rates
# 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.

Python — Error Handling with Retry
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.