USPS v3 SDK for Python, Node.js & PHP
Open Source, Production-Ready
All three SDKs are live on PyPI, npm, and Packagist. MIT licensed. Automatic OAuth token management. Typed responses. This is the definitive guide to integrating the USPS v3 REST API without writing boilerplate.
Installation
Pick your language. Both SDKs are a single package install with no configuration files, no YAML, and no environment scaffolding. Credentials go in the constructor.
# Requires Python 3.8+
pip install usps-v3 # Requires Node.js 18+ (zero dependencies)
npm install usps-v3 # Requires PHP 8.1+ (ext-json, ext-curl)
composer require revaddress/usps-v3-php
Python pulls in httpx as its only dependency. The Node.js SDK has zero dependencies — it uses the built-in fetch API available in Node 18+. The PHP SDK requires ext-json and ext-curl (standard on most PHP installs) and supports Magento 2, WooCommerce, and Laravel.
Quick Start: Address Validation
Address validation is the most common USPS API call and it is completely free. Both SDKs produce identical results — a standardized address with DPV confirmation, ZIP+4, and vacancy status.
from usps_v3 import Client # or: from usps_v3 import USPSClient
client = Client(
client_id="your_client_id",
client_secret="your_client_secret",
)
# Validate an address (free endpoint)
result = client.addresses.validate(
street_address="1600 Pennsylvania Ave NW",
city="Washington",
state="DC",
zip_code="20500",
)
print(result.address.street_address) # 1600 PENNSYLVANIA AVE NW
print(result.address.zip_plus_4) # 0005
print(result.address.dpv_confirmation) # Y = confirmed deliverable import { USPSClient } from 'usps-v3';
const client = new USPSClient({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
});
// Validate an address (free endpoint)
const result = await client.addresses.validate({
streetAddress: '1600 Pennsylvania Ave NW',
city: 'Washington',
state: 'DC',
zipCode: '20500',
});
console.log(result.address.streetAddress); // 1600 PENNSYLVANIA AVE NW
console.log(result.address.zipPlus4); // 0005
console.log(result.address.dpvConfirmation); // Y = confirmed deliverable use RevAddress\USPSv3\Client;
$usps = new Client(
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
);
// Validate an address (free endpoint)
$result = $usps->validateAddress([
'streetAddress' => '1600 Pennsylvania Ave NW',
'city' => 'Washington',
'state' => 'DC',
'ZIPCode' => '20500',
]);
echo $result['address']['streetAddress']; // 1600 PENNSYLVANIA AVE NW
echo $result['address']['ZIPPlus4']; // 0005
echo $result['address']['DPVConfirmation']; // Y = confirmed deliverable Note on exports: The Python SDK exports both Client and USPSClient (alias). Use whichever reads better in your codebase. The Node.js SDK exports USPSClient.
Feature Comparison: Python vs Node.js vs PHP
All three SDKs have full coverage of the USPS v3 API. The differences are in language runtime and HTTP internals, not functionality.
| Feature | Python usps-v3 | Node.js usps-v3 | PHP usps-v3-php |
|---|---|---|---|
| Address Validation | Yes | Yes | Yes |
| Package Tracking | Yes | Yes | Yes |
| Label Creation | Yes | Yes | Yes |
| Price Shopping | Yes | Yes | Yes |
| Service Standards | Yes | Yes | Yes |
| Auto OAuth Tokens | Yes | Yes | Yes (disk-cached) |
| Typed Responses | dataclasses + type hints | Full TypeScript definitions | Associative arrays |
| Typed Exceptions | Yes | Yes | Yes |
| HTTP Engine | httpx (async-ready) | Built-in fetch | ext-curl |
| Dependencies | 1 (httpx) | 0 | 0 |
| Min Runtime | Python 3.8+ | Node.js 18+ | PHP 8.1+ |
| Sandbox / Testing | environment="testing" | environment: 'testing' | environment: 'testing' |
| Framework Support | Django, Flask, FastAPI | Express, Fastify, Hono | Magento 2, WooCommerce, Laravel |
| License | MIT | MIT | MIT |
Free vs Paid API Endpoints
USPS splits its v3 API into free and paid tiers. The SDK doesn't gate anything — all methods are available — but the USPS API itself will reject paid-tier calls unless your account has a Business license with Payment Authorization.
| Endpoint | SDK Method | Tier |
|---|---|---|
| Address Validation | addresses.validate() | Free |
| Package Tracking | tracking.track() | Free |
| Service Standards | service_standards.* | Free |
| Label Creation | labels.create() | Paid |
| Domestic Prices | prices.domestic() | Paid |
| International Prices | prices.international() | Paid |
Getting started for free: Register at developer.usps.com, create an app, and you immediately get access to address validation, tracking, and service standards. No credit card, no approval wait.
SDK vs Raw API: Side-by-Side
Here is what address validation looks like with raw fetch versus the SDK. The raw version requires you to manage OAuth tokens, construct query parameters with USPS-specific casing, and parse nested JSON responses. The SDK does all of this for you.
// Step 1: Get an OAuth token (expires in 8 hours)
const tokenRes = await fetch('https://apis.usps.com/oauth2/v3/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'client_credentials',
client_id: 'your_client_id',
client_secret: 'your_client_secret',
}),
});
const { access_token } = await tokenRes.json();
// Step 2: Make the API call
const params = new URLSearchParams({
streetAddress: '1600 Pennsylvania Ave NW',
city: 'Washington',
state: 'DC',
ZIPCode: '20500',
});
const res = await fetch(
`https://apis.usps.com/addresses/v3/address?${params}`,
{ headers: { Authorization: `Bearer ${access_token}` } },
);
const data = await res.json();
// Step 3: Handle errors yourself
// Step 4: Cache and refresh tokens yourself
// Step 5: Parse nested response objects yourself
// ... or use the SDK and skip steps 1-5 import { USPSClient } from 'usps-v3';
const client = new USPSClient({
clientId: 'your_client_id',
clientSecret: 'your_client_secret',
});
const result = await client.addresses.validate({
streetAddress: '1600 Pennsylvania Ave NW',
city: 'Washington',
state: 'DC',
zipCode: '20500',
});
// OAuth handled. Errors typed. Response parsed. Done. The SDK eliminates OAuth management, parameter serialization, error parsing, and response typing. For every endpoint, not just address validation.
Package Tracking (Both Languages)
Tracking is free and both SDKs return the same data: current status, delivery date, and a full event history with timestamps and locations.
# Track a package (free endpoint)
tracking = client.tracking.track(
tracking_number="9400111899223456789012",
)
print(tracking.status) # "Delivered"
print(tracking.delivery_date) # "2026-03-08"
for event in tracking.events:
print(f"{event.date} | {event.city}, {event.state} | {event.description}") // Track a package (free endpoint)
const tracking = await client.tracking.track({
trackingNumber: '9400111899223456789012',
});
console.log(tracking.status); // "Delivered"
console.log(tracking.deliveryDate); // "2026-03-08"
for (const event of tracking.events) {
console.log(`${event.date} | ${event.city}, ${event.state} | ${event.description}`);
} // Track a package (free endpoint)
$tracking = $usps->trackPackage('9400111899223456789012');
echo $tracking['statusCategory']; // "Delivered"
echo $tracking['deliveryDate']; // "2026-03-08"
foreach ($tracking['trackingEvents'] as $event) {
echo "{$event['eventDate']} | {$event['eventCity']}, {$event['eventState']} | {$event['eventType']}";
} Labels & Pricing (Paid Tier)
Label creation and price shopping require a USPS Business account with Payment Authorization and a CRID/MID enrollment. The SDK handles the API calls identically — the only difference is your account permissions.
# Create a shipping label (paid — requires USPS Business account)
label = client.labels.create(
from_address={
"firm_name": "Acme Corp",
"street_address": "10001 Main St",
"city": "New York",
"state": "NY",
"zip_code": "10001",
},
to_address={
"street_address": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zip_code": "90001",
},
weight=2.5,
mail_class="PRIORITY_MAIL",
)
print(label.tracking_number) # 9205590123456789012345
print(label.label_url) # URL to download label PDF // Get domestic shipping prices (paid — requires USPS Business account)
const rates = await client.prices.domestic({
originZip: '10001',
destinationZip: '90210',
weight: 2.5,
length: 12,
width: 8,
height: 6,
});
for (const rate of rates.rates) {
console.log(`${rate.mailClass} — $${rate.totalPrice} (${rate.deliveryDays} days)`);
}
// Priority Mail Express — $32.75 (1 days)
// Priority Mail — $12.10 (2 days)
// USPS Ground Advantage — $8.45 (5 days) Need help with enrollment? See the CRID & MID Enrollment Guide for a step-by-step walkthrough of Business account setup, EPA registration, and Payment Authorization.
Go Deeper
This page covers the cross-language overview. For full endpoint reference, error handling patterns, and advanced usage, see the language-specific quickstart guides:
- Python Quickstart Guide — Full error handling, retry patterns, rate shopping, and DPV reference table.
- Node.js Quickstart Guide — TypeScript usage, label creation, async patterns, and full error hierarchy.
- PHP Quickstart Guide — Composer install, Magento 2 carrier module, WooCommerce integration, Laravel service provider.
- Full API Documentation — Complete endpoint reference with request/response schemas for all 41 routes.
- Rate Limit Strategies — Caching, queuing, and architecture patterns for production workloads beyond 60 req/hr.
- Source Code on GitHub — MIT licensed. Issues and contributions welcome.
Hit the 60 req/hr wall?
RevAddress provides a managed USPS v3 API with 600 req/min, built-in response caching, automatic OAuth, and bring-your-own-key support. Same SDKs, just point them at RevAddress.
Related Articles
Python Quickstart: usps-v3 SDK
Install and start validating addresses, tracking packages, and comparing rates.
Node.js Quickstart: usps-v3 SDK
Full TypeScript support with automatic OAuth token management.
PHP Quickstart: usps-v3-php SDK
Composer install with Magento 2, WooCommerce, and Laravel integration.
usps-api / usps-webtools Are Dead
The old packages are permanently broken. One-command migration to usps-v3.