Skip to content
All posts

USPS API Rate Limits in 2026: From 6,000/min to 60/hour

Updated 12 min read By RevAddress

Topics: Rate limits, USPS v3, Troubleshooting

On January 25, 2026, USPS retired the Web Tools XML API and moved everyone to v3 REST. The legacy API handled roughly 6,000 requests per minute without throttling. The replacement allows 60 requests per hour, a 6,000x reduction that turns a ten-minute batch job into a hundred-hour one. Production systems design for it.

At 60 requests an hour, a mid-size Shopify store doing 200 orders a day will exceed the limit during peak hours. Address validation, rate shopping, label creation, and tracking all draw on the same quota. The five patterns below are built for those peaks.

From 6,000 requests a minute to 60 an hour

The old Web Tools API was an unmetered XML gateway, with no OAuth, no rate limits and no per-application tracking. You sent XML, you got XML back, and the server did not care how fast you sent it. Developers observed sustained throughput of about 6,000 requests per minute with no 429 responses.

Before and after
Legacy Web Tools (retired Jan 25, 2026)
Rate limit: none enforced
Observed:   ~6,000 requests/min (~100/sec)
Auth:       User ID in XML body (no OAuth)
Format:     XML request -> XML response

USPS v3 REST API (current)
Rate limit: 60 requests/hour (default)
That is:    1 request per minute
Auth:       OAuth 2.0 client_credentials
Format:     JSON request -> JSON response

Reduction factor: 6,000x

Why USPS did it: the legacy API had no visibility into who was calling what: no authentication, no per-app metering and no abuse prevention. The v3 API meters per application through OAuth 2.0, which gives USPS granular control over capacity and underpins the April 2026 Access Control initiative.

The 60/hr default is a starting point rather than a ceiling, and getting from it to production throughput takes architecture.

API calls per order at peak hour

A single e-commerce order touches the USPS API 3–5 times: address validation at checkout, rate shopping across one to three services, label creation, and tracking registration.

Daily orders API calls/day Peak hour need vs 60/hr limit
50 150–250 ~30 req/hr Under limit
200 600–1,000 ~120 req/hr 2x over
500 1,500–2,500 ~300 req/hr 5x over
2,000 6,000–10,000 ~1,200 req/hr 20x over

The peak-hour column is the one that matters. Orders do not arrive evenly across 24 hours; they cluster between 10 AM and 6 PM with spikes at lunch and after work. A store doing 200 orders a day that takes 40 of them in one hour generates 120+ calls against a 60/hr budget.

A 10,000-address batch that finished in about two minutes under Web Tools now needs roughly 167 hours (nearly seven days) at 60 requests an hour.

Requesting a higher rate limit from USPS

USPS raises the limit by manual request and evaluates each one by hand; it publishes no guaranteed outcome or turnaround.

  1. Submit a request via emailus.usps.com. Include your CRID, application name, estimated monthly volume (be specific: “5,000 address validations + 2,000 labels/month”), and a business justification for the throughput.
  2. Wait 1–5 business days. There is no SLA. Some developers report same-day approval, others wait over a week, and no criteria for approval or denial are published.
  3. Expect a tier, not a number. Reported increases cluster around 300 req/hr for small business, 1,000 req/hr for mid-volume, and 5,000+ for enterprise. These are community observations and our own experience, not published tiers.

An increase complements the patterns below, which work on the default limit while the request is pending.

Five patterns for the 60-per-hour limit

They work independently or together. Run at least the first three in production.

Pattern 1: Aggressive address caching

A validated USPS address does not change. If a customer enters 1600 Pennsylvania Ave NW today, USPS returns the same standardized result next month. Cache validation results for 30 days and 60–80% of address calls disappear.

Python: address cache with SHA-256 keying Python
import hashlib, time
from typing import Optional, Any


class AddressCache:
  """30-day TTL cache keyed by normalized address string."""
  TTL = 30 * 86400  # 30 days in seconds

  def __init__(self):
      self._store: dict[str, dict] = {}

  def _key(self, street: str, city: str, state: str, zip_code: str) -> str:
      raw = f"{street}|{city}|{state}|{zip_code}".upper().strip()
      return hashlib.sha256(raw.encode()).hexdigest()

  def get(self, street: str, city: str, state: str, zip_code: str) -> Optional[Any]:
      key = self._key(street, city, state, zip_code)
      entry = self._store.get(key)
      if entry and time.time() - entry["ts"] < self.TTL:
          return entry["data"]  # Cache hit — no API call
      return None  # Miss — call USPS

  def set(self, street: str, city: str, state: str, zip_code: str, result: Any):
      key = self._key(street, city, state, zip_code)
      self._store[key] = {"data": result, "ts": time.time()}


# Usage with the usps-v3 SDK
from usps_v3 import USPSClient

cache = AddressCache()
client = USPSClient(client_id="...", client_secret="...")


def validate_address(street, city, state, zip_code):
  cached = cache.get(street, city, state, zip_code)
  if cached:
      return cached  # 0 API calls, instant

  result = client.addresses.validate(
      street_address=street, city=city, state=state, zip_code=zip_code
  )
  cache.set(street, city, state, zip_code, result)
  return result

What to cache and what not to: cache address validation for 30 days, city/state lookups indefinitely, and service standards for 7 days. Do not cache tracking data (stale within minutes), prices (they move with rate adjustments), or labels (one-time tokens).

Pattern 2: Queue-based rate limiting with exponential backoff

Rather than sending requests and hoping you are under the limit, queue every call and process at a controlled rate. On a 429, back off exponentially instead of retrying immediately.

Python: async rate-limited queue with 429 handling Python
import asyncio, time, random
from usps_v3 import USPSClient
from usps_v3.exceptions import RateLimitError


class RateLimitedQueue:
  """Process USPS API calls at a controlled rate."""

  def __init__(self, client: USPSClient, max_per_hour: int = 55):
      self.client = client
      self.interval = 3600 / max_per_hour  # seconds between calls
      self.queue: asyncio.Queue = asyncio.Queue()
      self.last_call = 0.0

  async def _wait_for_slot(self):
      elapsed = time.time() - self.last_call
      if elapsed < self.interval:
          await asyncio.sleep(self.interval - elapsed)
      self.last_call = time.time()

  async def _execute_with_backoff(self, fn, *args, max_retries=3):
      for attempt in range(max_retries):
          try:
              await self._wait_for_slot()
              return fn(*args)
          except RateLimitError:
              wait = (2 ** attempt) + random.uniform(0, 1)
              await asyncio.sleep(wait)  # 2s, 4s, 8s + jitter
      raise RateLimitError("Exhausted retries")

  async def validate_address(self, **kwargs):
      return await self._execute_with_backoff(
          self.client.addresses.validate, **kwargs
      )

max_per_hour=55 leaves a five-request buffer below the limit. The jitter prevents a thundering herd when several workers recover at once.

Pattern 3: Batch scheduling with sub-batch windowing

For work that is not real-time (nightly address cleanup, bulk tracking updates), chunk it into sub-batches that fit the rate limit window.

Node.js: sub-batch processor JavaScript
import { USPSClient } from 'usps-v3';

const client = new USPSClient({
clientId: process.env.USPS_CLIENT_ID,
clientSecret: process.env.USPS_CLIENT_SECRET,
});

async function processBatch(addresses, ratePerHour = 55) {
const delayMs = Math.ceil(3_600_000 / ratePerHour);
const results = [];

for (const addr of addresses) {
  try {
    const result = await client.addresses.validate({
      streetAddress: addr.street,
      city: addr.city,
      state: addr.state,
      zipCode: addr.zip,
    });
    results.push({ ...addr, validated: result, error: null });
  } catch (err) {
    results.push({ ...addr, validated: null, error: err.message });
  }
  await new Promise(r => setTimeout(r, delayMs));
}

return results;
}

// 10,000 addresses at 55/hr = ~182 hours
// With caching: ~60% hit rate -> ~73 hours
// With a rate limit increase (300/hr): ~13 hours

Schedule these during off-peak hours, roughly 2–6 AM, when real-time checkout calls are not competing for the same budget. Combine with caching so you never re-validate an address you have already seen.

Pattern 4: An SDK that owns the token lifecycle

The usps-v3 SDK handles the detail a raw HTTP integration writes by hand: the 8-hour OAuth token, refreshed 30 minutes before it expires. A 429 comes back as RateLimitError carrying USPS’s Retry-After value, so the backoff from Pattern 2 wraps the call.

pip install usps-v3

import time
from usps_v3 import Client, RateLimitError

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

def validate(**address):
  for attempt in range(3):
      try:
          return client.addresses.validate(**address)  # token refresh: automatic
      except RateLimitError as e:
          time.sleep(e.retry_after or 2 ** (attempt + 1))
  raise RuntimeError("USPS still rate limiting after 3 attempts")

result = validate(
  street_address="1600 Pennsylvania Ave NW",
  city="Washington",
  state="DC",
  zip_code="20500",
)

The Python SDK is on PyPI and GitHub; the Node.js SDK is on npm and GitHub.

Pattern 5: An API proxy that smooths the limit

If you need materially higher practical throughput today without waiting on a USPS quota decision, a managed proxy handles rate limiting, caching, token management, and retries at the infrastructure layer. Your code stays simple and the complexity moves upstream. RevAddress, which publishes this site, runs one.

RevAddress API: no rate limit management in your code JavaScript
const response = await fetch('https://api.revaddress.com/api/address/validate', {
method: 'POST',
headers: {
  'X-API-Key': apiKey,
  'Content-Type': 'application/json',
},
body: JSON.stringify({
  streetAddress: '1600 Pennsylvania Ave NW',
  city: 'Washington',
  state: 'DC',
  ZIPCode: '20500',
}),
});

const result = await response.json();
// No 429 handling. No token refresh. No cache layer.

With BYOK, requests route through your own USPS credentials, so a rate limit increase granted to you applies through the proxy and your data stays isolated in per-merchant Durable Objects with AES-GCM encryption.

Free API key

Test the patterns against a real key

A free RevAddress key gives you 1,000 requests a month of Census-based validation with no USPS license, enough to build and prove your caching and queueing before you are fighting a 60/hr budget in production.

Accept the service terms and complete the bot check to create your key.

Which pattern when

Pattern Implementation Effective throughput Best for
Address caching 30 minutes 60–80% call reduction Everyone, as the first step
Queue + backoff 2–4 hours Smooth within 60/hr Bursty checkout flows
Batch windowing 1 hour Spread across off-peak Nightly jobs, list cleanup
Rate limit increase 1 email 300–5,000 req/hr Growing businesses (1–5 day wait)
API proxy 15 minutes Managed throughput Need throughput today

The recommended stack is caching, a backoff around the SDK call and a rate limit increase request, each a small code change. Add a proxy layer when you need throughput immediately or want the complexity out of your codebase entirely.

April 2026 Access Control

The USPS API Access Control initiative, live since April 1, 2026, restricts how third-party platforms reach tracking data. If you are a 3PL, software platform, or service provider tracking packages for clients, it applies to you.

  • MID linking: service providers need Mailer IDs linked to their USPS application for tracking access.
  • Bulk tracking restrictions: large-scale tracking feeds face new authorization requirements.

Build the rate-limiting layer so a USPS policy change lands in one place. Caching and BYOK both do that. The full analysis is in USPS April 2026 Access Control.

The August 1, 2026 Addresses API license fee

Since August 1, 2026, address validation sits behind a signed USPS license with a consumption fee attached: $10 flat for up to 2,000 lookups a month, rising per 1,000 above that. Caching now saves money as well as headroom. The pricing guide has the full curve and the license process.

Questions

What is the USPS v3 API rate limit in 2026?
The USPS v3 REST API enforces a default rate limit of 60 requests per hour per application. It applies across all endpoints: address validation, tracking, pricing, and label creation share the same window. The legacy Web Tools API enforced no rate limit and sustained roughly 6,000 requests per minute.
Why did USPS reduce API rate limits so drastically?
USPS moved from an unmetered XML gateway to an OAuth 2.0 REST architecture. The v3 API meters per application to prevent abuse, allocate capacity fairly, and support the API Access Control initiative that landed in April 2026.
How do I handle USPS API 429 errors in production?
Use exponential backoff with jitter on 429 responses: start at 2 seconds, double each retry, cap at 3 attempts. Combine it with aggressive address caching, where a 30-day TTL removes 60-80% of calls, and with request queuing that smooths peak-hour bursts.
Can I get higher USPS API rate limits?
Yes, by request rather than self-service. Contact USPS through emailus.usps.com with your CRID, app name, estimated monthly volume, and business justification. Reported increases run from roughly 300 requests per hour for small businesses to 5,000+ for enterprise, with responses in 1-5 business days and no published criteria.
Does the rate limit apply per endpoint or per application?
Per application. Address validation, rate shopping, label creation, and tracking all draw from the same 60-requests-per-hour budget, which is why a checkout flow making four calls per order exhausts it at 15 orders an hour.