Skip to content
All Posts
Migration Guide

USPS Web Tools Migration: XML to v3 REST, End to End

·Updated ·12 min read·By RevAddress·Migration Guide

USPS retired the Web Tools XML API on January 25, 2026. Everything that POSTed XML to secure.shippingapis.com or production.shippingapis.com stopped returning data that day, with no grace period and no redirect. The shutdown post covers what broke and when; this one is the migration itself.

The surface is smaller than it looks. Every operation you had still exists. What changed is the transport, the authentication, the field names, and the throughput ceiling.

What actually changed

Web Tools (retired) USPS v3 REST
Format XML request and response JSON request and response
Auth USERID in the query string OAuth 2.0 bearer token, 8-hour lifetime
Base URL secure.shippingapis.com apis.usps.com
Testing Same host, test USERID apis-tem.usps.com
Rate limit None enforced 60 requests per hour, per app
Cost Free Addresses licensed and metered since August 1, 2026

The last row is the one that has changed since most migration guides were written. Addresses now sit behind a signed license agreement and a consumption-tier fee curve; tracking, pricing, and service standards do not. What the USPS API actually costs has the published curve and the enrollment sequence, which runs at legal-department speed and belongs before your launch date rather than after it.

Endpoint mapping

Legacy XML v3 REST
Verify (AddressValidateRequest) GET /addresses/v3/address
CityStateLookup GET /addresses/v3/city-state
ZipCodeLookup GET /addresses/v3/zipcode
TrackV2 (TrackFieldRequest) GET /tracking/v3/tracking/{trackingNumber}
RateV4 POST /prices/v3/total-rates/search
eVS (CreateLabel) POST /labels/v3/label

Two of those are traps. RateV4 was a GET with the request XML crammed into a query parameter; the v3 equivalent is a real POST with a JSON body, so the port is not a URL swap. And eVS needed only a USERID, while POST /labels/v3/label needs a CRID, Mailer IDs, and a funded Enterprise Payment Account on top of OAuth. Labels are the one operation where the migration is an enrollment project rather than a code change.

OAuth 2.0 setup

Register an app at developer.usps.com and copy the Consumer Key and Consumer Secret. The secret is displayed once.

Then exchange them for a token. The single most common failure here is sending JSON: the token endpoint takes form-encoded data and answers a JSON body with a 401 if you get it wrong.

import httpx

# Exchange client credentials for an access token.
resp = httpx.post(
  "https://apis.usps.com/oauth2/v3/token",
  data={
      "grant_type": "client_credentials",
      "client_id": YOUR_CLIENT_ID,
      "client_secret": YOUR_CLIENT_SECRET,
      "scope": "addresses tracking prices labels",
  },
)
token = resp.json()["access_token"]
# expires_in is 28800 seconds — 8 hours.

Cache the token. A fresh token request per API call is not just wasteful, it draws on the same 60-per-hour window as everything else, so a busy integration can rate-limit itself purely on authentication. Refresh about 30 minutes before expiry and retry once on a 401.

The field swap that corrupts data silently

This is the defect that survives testing, because nothing errors.

Web Tools used Address2 for the street line and Address1 for the apartment or suite. That was backwards from every other address API in existence, including the one that replaced it. In v3, streetAddress is the street and secondaryAddress is the unit.

Web Tools Contains v3 field
Address2 1600 Pennsylvania Ave NW streetAddress
Address1 Apt 4B secondaryAddress

Port by meaning, not by field order. A mapping that reads “Address1 goes to the first v3 address field” puts apartment numbers where the street belongs, and USPS answers with a plausible-looking failure to match rather than an error you can catch.

Before and after

The same lookup, both ways.

Before — Web Tools XML (retired)xml
POST https://secure.shippingapis.com/ShippingAPI.dll?API=Verify

<AddressValidateRequest USERID="YOUR_USERID">
<Address>
  <Address1/>
  <Address2>1600 Pennsylvania Ave NW</Address2>
  <City>Washington</City>
  <State>DC</State>
  <Zip5>20500</Zip5>
  <Zip4/>
</Address>
</AddressValidateRequest>
After — v3 RESTbash
curl -G "https://apis.usps.com/addresses/v3/address" \
-H "Authorization: Bearer $TOKEN" \
--data-urlencode "streetAddress=1600 Pennsylvania Ave NW" \
--data-urlencode "city=Washington" \
--data-urlencode "state=DC" \
--data-urlencode "ZIPCode=20500"

# {
#   "address": {
#     "streetAddress": "1600 PENNSYLVANIA AVE NW",
#     "city": "WASHINGTON",
#     "state": "DC",
#     "ZIPCode": "20500",
#     "ZIPPlus4": "0005"
#   },
#   "additionalInfo": { "DPVConfirmation": "Y" }
# }

Note ZIPCode and ZIPPlus4, both with a capitalised ZIP. The v3 schema is camelCase everywhere except the ZIP fields, and that inconsistency is a reliable source of silent nulls in hand-written mappers.

The rate limit is the real wall

USPS caps a registered application at 60 requests per hour, shared across every endpoint it calls. Address validation, tracking, rates, labels, and token requests all draw from the same window.

A store doing 50 orders a day is not a 50-request-a-day integration. Checkout fires validation on address changes, rate shopping runs on cart updates, and tracking polls run on a schedule. The daily average is comfortably under the ceiling and the peak hour is not, which is why the failure shows up as an empty shipping block at 2pm rather than as a steady error rate.

Four things work, in this order of effort:

  1. Cache what cannot change. A validated address does not change. Cache it for 30 days and repeat lookups stop reaching USPS entirely.
  2. Smooth the peak. Queue non-interactive work, such as tracking polls, and process at a steady rate so it does not compete with checkout traffic.
  3. Ask for more. Email USPS through emailus.usps.com with your CRID, app name, and real usage figures. Increases are granted case by case.
  4. Put managed infrastructure in front. Connect your own USPS license to RevAddress (BYOK) and the OAuth lifecycle, caching, retries, and budgeting against the window are handled for you, on your credentials and your quota.

The rate-limit architecture guide has working cache and queue code for the first two.

Test before you cut over

Point at apis-tem.usps.com first. Same credentials, same OAuth flow, different host. Tokens are not portable between environments, so clear any cached token when you switch, or you will spend an afternoon debugging a 401 that is really a wrong-environment token.

Before you call the migration done:

  1. A known-good address returns a ZIPPlus4 and DPVConfirmation: Y.
  2. A known-bad address returns a failure you can catch, not an exception that escapes.
  3. An address with a unit round-trips with the unit still in secondaryAddress.
  4. Your integration survives more than 8 hours unattended, which proves token refresh works.
  5. A deliberate burst past 60 requests produces a handled 429, not an empty response rendered to a customer.

Start here

Questions

Is USPS Web Tools still available?
No. USPS retired the Web Tools XML platform on January 25, 2026. Calls to secure.shippingapis.com and production.shippingapis.com fail. There is no grace period and no redirect, so any integration still building XML around a USERID parameter is dead code.
What replaced the USERID parameter in the v3 API?
OAuth 2.0 client credentials. You register an app at the USPS Developer Portal, receive a Consumer Key and Consumer Secret, and POST them to https://apis.usps.com/oauth2/v3/token with Content-Type application/x-www-form-urlencoded. The response carries an access token valid for 28800 seconds, which is 8 hours.
Which Web Tools field becomes streetAddress in v3?
Address2, not Address1. Web Tools used Address2 for the street line and Address1 for the apartment or suite, which is the reverse of every other address API. In v3, streetAddress is the street and secondaryAddress is the unit. Porting the old order by name puts apartment numbers in the street field and fails silently.
Is the v3 API free now that Web Tools is gone?
Not for addresses. Since August 1, 2026 the Addresses API requires a signed license agreement and bills on a consumption-tier curve. Tracking, pricing, and service standards sit outside that fee curve. Budget signature time before your launch date, not after.
What is the v3 rate limit and can it be raised?
60 requests per hour per application by default, shared across every endpoint the app calls. You can request an increase through emailus.usps.com with your CRID, app name, and real usage figures. Until it is granted, caching is the only thing standing between your checkout and a 429.