Skip to content
·8 min read·Replacement Guide

usps-api / usps-webtools Are Dead
Here’s the 2026 Replacement

The usps-api PyPI package, usps-webtools, and usps-webtools-promise all target the USPS Web Tools XML API — which was permanently shut down on January 25, 2026. If you are still using them, your integration is broken right now. The replacement is one command away.

What Happened

On January 25, 2026, USPS shut down the Web Tools XML API at secure.shippingapis.com. This was the endpoint that every one of these packages depended on. The domain no longer resolves. There is no redirect. Every API call through these packages now fails:

PackageRegistryDownloads/moLast UpdatedStatus
usps-apiPyPIStill actively downloadedJuly 2020Dead
usps-webtoolsnpmStill actively downloaded2018Dead
usps-webtools-promisenpmStill actively downloaded2019Dead

These packages still see meaningful download activity from developers who do not realize the underlying API is gone. The packages install fine. They import fine. They just fail at runtime.

The Error You’re Seeing

If you are using any of these packages, you are hitting one of these errors right now:

Python — usps-api (broken)
# usps-api — BROKEN since January 25, 2026
from usps import USPSApi

usps = USPSApi("YOUR_USER_ID")
address = usps.validate_address(
    "1600 Pennsylvania Ave NW",
    "Washington",
    "DC",
)

# ConnectionError: HTTPSConnectionPool(host='secure.shippingapis.com')
# Max retries exceeded — USPS Web Tools is GONE
Node.js — usps-webtools (broken)
// usps-webtools — BROKEN since January 25, 2026
const USPS = require('usps-webtools');

const usps = new USPS({ userId: 'YOUR_USER_ID' });

usps.verify({
  street1: '1600 Pennsylvania Ave NW',
  city: 'Washington',
  state: 'DC',
}, (err, address) => {
  // Error: getaddrinfo ENOTFOUND secure.shippingapis.com
  // OR: XML parse error — empty response body
});

The host secure.shippingapis.com is permanently offline. No amount of retries, timeout increases, or version pinning will fix this. The API itself is gone.

The Fix: One Command

The replacement is the usps-v3 SDK, which targets the new USPS v3 REST API at apis.usps.com. Same USPS data, same endpoints — just REST + JSON instead of SOAP + XML.

Migration (30 seconds)
# Python migration (30 seconds)
pip uninstall usps-api
pip install usps-v3

# Node.js migration (30 seconds)
npm uninstall usps-webtools usps-webtools-promise
npm install usps-v3
Old Packagesusps-v3
API Targetsecure.shippingapis.com (dead)apis.usps.com (live)
Data FormatSOAP + XMLREST + JSON
AuthenticationUser ID in query stringOAuth 2.0 (automatic)
Typed ResponsesNoYes
Error HandlingParse XML error stringsTyped exceptions (401, 429, etc.)
MaintainedNo (2018–2020)Yes (2026)

Credentials change: The old packages used a USPS User ID passed in the query string. The new API uses OAuth 2.0 with a Client ID and Client Secret from developer.usps.com. Registration is free. The SDK manages tokens automatically.

Address Validation: Old vs New

Address validation was the most common use of usps-api and usps-webtools. Here is the side-by-side migration for both languages. The new code is cleaner, typed, and actually works.

Python: usps-api → usps-v3

Before — usps-api (broken)
# usps-api — BROKEN since January 25, 2026
from usps import USPSApi

usps = USPSApi("YOUR_USER_ID")
address = usps.validate_address(
    "1600 Pennsylvania Ave NW",
    "Washington",
    "DC",
)

# ConnectionError: HTTPSConnectionPool(host='secure.shippingapis.com')
# Max retries exceeded — USPS Web Tools is GONE
After — usps-v3 (works)
# usps-v3 — the 2026 replacement
from usps_v3 import USPSClient  # also: from usps_v3 import Client

client = USPSClient(
    client_id="your_client_id",
    client_secret="your_client_secret",
)

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.city)               # WASHINGTON
print(result.address.zip_plus_4)         # 0005
print(result.address.dpv_confirmation)   # Y = confirmed deliverable

Node.js: usps-webtools → usps-v3

Before — usps-webtools (broken)
// usps-webtools — BROKEN since January 25, 2026
const USPS = require('usps-webtools');

const usps = new USPS({ userId: 'YOUR_USER_ID' });

usps.verify({
  street1: '1600 Pennsylvania Ave NW',
  city: 'Washington',
  state: 'DC',
}, (err, address) => {
  // Error: getaddrinfo ENOTFOUND secure.shippingapis.com
  // OR: XML parse error — empty response body
});
After — usps-v3 (works)
// usps-v3 — the 2026 replacement
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',
});

console.log(result.address.streetAddress);    // 1600 PENNSYLVANIA AVE NW
console.log(result.address.city);              // WASHINGTON
console.log(result.address.zipPlus4);          // 0005
console.log(result.address.dpvConfirmation);   // Y = confirmed deliverable

Key differences: The old packages used a User ID string. The new SDK uses OAuth Client ID + Client Secret (managed automatically). Python uses street_address (snake_case). Node uses streetAddress (camelCase). Both SDKs return typed response objects — no more parsing raw XML or JSON dictionaries.

Package Tracking: Old vs New

Tracking was the second most common use case. The old packages returned XML that you had to parse yourself. The new SDK returns typed objects with status, delivery date, and full event history.

Python — Old (broken)
# OLD: usps-api tracking — DEAD
from usps import USPSApi

usps = USPSApi("YOUR_USER_ID")
tracking = usps.track("9400111899223456789012")
# ConnectionError: secure.shippingapis.com is gone
Node.js — Old (broken)
// OLD: usps-webtools tracking — DEAD
const USPS = require('usps-webtools');
const usps = new USPS({ userId: 'YOUR_USER_ID' });

usps.track('9400111899223456789012', (err, result) => {
  // Error: ENOTFOUND secure.shippingapis.com
});
Python — New (works)
# NEW: usps-v3 tracking — works
from usps_v3 import USPSClient

client = USPSClient(
    client_id="your_client_id",
    client_secret="your_client_secret",
)

tracking = client.tracking.get(
    tracking_number="9400111899223456789012",
)

print(tracking.status)           # "Delivered"
print(tracking.status_category)  # "Delivered"
print(tracking.delivery_date)    # "2026-03-08"

for event in tracking.events:
    print(f"{event.date} | {event.city}, {event.state} | {event.description}")
Node.js — New (works)
// NEW: usps-v3 tracking — works
import { USPSClient } from 'usps-v3';

const client = new USPSClient({
  clientId: 'your_client_id',
  clientSecret: 'your_client_secret',
});

const tracking = await client.tracking.get({
  trackingNumber: '9400111899223456789012',
});

console.log(tracking.status);          // "Delivered"
console.log(tracking.statusCategory);  // "Delivered"
console.log(tracking.deliveryDate);    // "2026-03-08"

for (const event of tracking.events) {
  console.log(`${event.date} | ${event.city}, ${event.state} | ${event.description}`);
}

Free tier: Both address validation and package tracking are completely free on the USPS v3 API. These are the two most common use cases from the old packages. No credit card, no business account — register at developer.usps.com and start calling.

Why 25,500 Devs/Month Are Still Installing Dead Packages

Three reasons:

  • CI/CD pipelines. Automated builds keep pulling these packages from lockfiles. The install succeeds. The app deploys. The USPS calls silently fail at runtime — and if you aren't monitoring USPS responses specifically, you don't notice until a customer reports broken tracking or address validation.
  • Outdated tutorials. Google "usps api python" and the top results still reference usps-api. Stack Overflow answers from 2021 link directly to the PyPI page. These results rank because they accumulated years of backlinks before the API died.
  • No deprecation notice. None of these packages were updated with a deprecation warning. The last usps-api release was July 2020 — five and a half years before the API shut down. The README still says "working" with no mention of the retirement.

What Changed Under the Hood

The USPS replaced its entire API stack. Same postal data, completely different protocol:

Web Tools (Dead)v3 REST API (Current)
Base URLsecure.shippingapis.comapis.usps.com
ProtocolSOAP / XMLREST / JSON
AuthUser ID (query param)OAuth 2.0 Bearer token
AddressesAddressValidateRequestGET /addresses/v3/address
TrackingTrackV2RequestGET /tracking/v3/tracking/{trackingNumber}
RatesRateV4RequestPOST /prices/v3/total-rates/search
Rate LimitUndocumented60 req/hr (documented, typed errors)

5-Minute Migration Checklist

Complete migration from any of these dead packages:

  1. 1Register at USPS Developer Portal. Go to developer.usps.com, create an account, and create an application. Copy your Client ID and Client Secret. Takes 2 minutes. Free.
  2. 2Swap the package. pip install usps-v3 or npm install usps-v3. Remove the old package from your lockfile.
  3. 3Update imports. Python: from usps_v3 import USPSClient. Node: import { USPSClient } from 'usps-v3'.
  4. 4Replace User ID with OAuth credentials. The old userId parameter is gone. Pass client_id and client_secret to the client constructor. Tokens are managed automatically.
  5. 5Update method calls. Use the code examples above. The parameter names changed slightly (street_address in Python, streetAddress in Node), but the data is the same.
  6. 6Deploy. That is it. Same USPS data, better developer experience, and it actually works.

usps-v3 SDK Details

Both SDKs are MIT licensed, open source, and cover the same API surface. Pick your language:

FeaturePython usps-v3Node.js usps-v3
Installpip install usps-v3npm install usps-v3
ExportsClient + USPSClientUSPSClient
Address ValidationFreeFree
Package TrackingFreeFree
Auto OAuth TokensYesYes
Typed Responsesdataclasses + type hintsFull TypeScript definitions
Typed ExceptionsYesYes
Dependencies1 (httpx)0
Min RuntimePython 3.8+Node.js 18+
LicenseMITMIT

Further Reading

Replace the broken package once, then move on

Start with the free proof wedge — validation, ZIP+4, and rates — then expand into protected workflows only when your shipping surface needs them.

Package tracking remains a protected route with public proof depth still gated.

Related Articles