Skip to content
All Posts
Tutorial

USPS Batch Address Validation API: Validate 50 Addresses in One Call

· 6 min read

Validating one address at a time works for a signup form. It doesn’t work when you’re cleaning a mailing list with 10,000 rows, importing a Shopify CSV, or running nightly data quality checks on your customer database.

RevAddress’s batch endpoint validates up to 50 addresses per call, processes them in parallel, and caches results for 24 hours so repeat lookups are free.

The endpoint

POST /api/batch/validate
curl -X POST "https://api.revaddress.com/api/batch/validate" \
-H "X-API-Key: rv_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
  "addresses": [
    {
      "streetAddress": "1600 Pennsylvania Ave NW",
      "city": "Washington",
      "state": "DC",
      "ZIPCode": "20500"
    },
    {
      "streetAddress": "350 Fifth Avenue",
      "city": "New York",
      "state": "NY",
      "ZIPCode": "10118"
    },
    {
      "streetAddress": "1 Infinite Loop",
      "city": "Cupertino",
      "state": "CA"
    }
  ]
}'

Each address needs at minimum a streetAddress. City, state, and ZIP are optional but improve match accuracy. The API validates all addresses in parallel — a single slow address never blocks the rest.

Response format

Batch response
{
"total": 3,
"successful": 3,
"failed": 0,
"results": [
  {
    "index": 0,
    "status": "success",
    "address": {
      "streetAddress": "1600 PENNSYLVANIA AVE NW",
      "city": "WASHINGTON",
      "state": "DC",
      "ZIPCode": "20500-0003"
    },
    "additionalInfo": {
      "DPVConfirmation": "Y",
      "carrierRoute": "C000",
      "deliveryPoint": "00",
      "vacant": "N"
    },
    "cached": false
  },
  {
    "index": 1,
    "status": "success",
    "address": {
      "streetAddress": "350 5TH AVE",
      "city": "NEW YORK",
      "state": "NY",
      "ZIPCode": "10118-0110"
    },
    "additionalInfo": {
      "DPVConfirmation": "Y",
      "carrierRoute": "B999",
      "deliveryPoint": "50",
      "vacant": "N"
    },
    "cached": false
  },
  {
    "index": 2,
    "status": "success",
    "address": {
      "streetAddress": "1 INFINITE LOOP",
      "city": "CUPERTINO",
      "state": "CA",
      "ZIPCode": "95014-2083"
    },
    "additionalInfo": {
      "DPVConfirmation": "Y",
      "carrierRoute": "C067",
      "deliveryPoint": "01",
      "vacant": "N"
    },
    "cached": false
  }
],
"usage": {
  "cached_hits": 0,
  "fresh_lookups": 3
}
}

The usage object tells you exactly how many addresses hit the cache vs required a fresh USPS lookup. Cached addresses don’t count against your monthly quota.

SDK examples

Batch validation in Python and Node.js
import requests

API_KEY = "rv_live_your_key_here"
BASE = "https://api.revaddress.com"

addresses = [
  {"streetAddress": "1600 Pennsylvania Ave NW", "city": "Washington", "state": "DC"},
  {"streetAddress": "350 Fifth Avenue", "city": "New York", "state": "NY"},
  {"streetAddress": "742 Evergreen Terrace", "city": "Springfield", "state": "IL"},
  # ... up to 50 per call
]

resp = requests.post(
  f"{BASE}/api/batch/validate",
  headers={"X-API-Key": API_KEY},
  json={"addresses": addresses},
)

data = resp.json()
print(f"Validated {data['successful']}/{data['total']} addresses")
print(f"Cache hits: {data['usage']['cached_hits']}")

for result in data["results"]:
  if result["status"] == "success":
      addr = result["address"]
      dpv = result["additionalInfo"]["DPVConfirmation"]
      print(f"  [{dpv}] {addr['streetAddress']}, {addr['city']}, {addr['state']} {addr['ZIPCode']}")
  else:
      print(f"  [ERR] index {result['index']}: {result['error']}")

Processing large lists

50 addresses per call is the max. For larger lists, chunk and send sequentially:

Processing 10,000 addresses in Python
import requests
import time

def validate_batch(addresses, api_key):
  resp = requests.post(
      "https://api.revaddress.com/api/batch/validate",
      headers={"X-API-Key": api_key},
      json={"addresses": addresses},
  )
  return resp.json()

# Load your address list
all_addresses = load_from_csv("customers.csv")  # Your loader

# Chunk into batches of 50
BATCH_SIZE = 50
results = []

for i in range(0, len(all_addresses), BATCH_SIZE):
  chunk = all_addresses[i:i + BATCH_SIZE]
  batch = validate_batch(chunk, "rv_live_your_key_here")
  results.extend(batch["results"])
  print(f"Processed {min(i + BATCH_SIZE, len(all_addresses))}/{len(all_addresses)}")

# Summary
deliverable = [r for r in results if r["status"] == "success"
             and r["additionalInfo"]["DPVConfirmation"] == "Y"]
print(f"Deliverable: {len(deliverable)}/{len(results)}")

The second time you run the same list, cached addresses return instantly and don’t count against your quota. This makes nightly validation runs practically free after the first pass.

Handling failures gracefully

Individual address failures never kill the batch. If one address can’t be validated (bad format, USPS timeout), the response marks that entry as "status": "error" while all other addresses succeed:

Mixed results
{
"total": 3,
"successful": 2,
"failed": 1,
"results": [
  { "index": 0, "status": "success", "address": { "..." : "..." }, "cached": false },
  { "index": 1, "status": "error", "error": "Validation failed", "address": null, "cached": false },
  { "index": 2, "status": "success", "address": { "..." : "..." }, "cached": true }
],
"usage": { "cached_hits": 1, "fresh_lookups": 2 }
}

This uses Promise.allSettled under the hood — the same resilience pattern used in production payment systems.

Pricing comparison

Batch validation is where RevAddress’s pricing advantage becomes dramatic:

ProviderPer validation10K addresses100K addresses
RevAddress (Growth $79/mo)$0.003$30$300
EasyPost$0.01$100$1,000
Shippo$0.05/label (includes validation)$500$5,000
Smarty$0.01$100$1,000
Lob$0.015$150$1,500

At 100K addresses, RevAddress is 3x cheaper than EasyPost and 17x cheaper than Shippo.

Growth tier ($79/month) includes 25,000 requests. Each cached address is free, so repeat validation of the same list costs nothing after the first run.

Requirements

  • Tier: Growth ($79/mo) or above. Free and Starter tiers use single address validation instead.
  • Max batch size: 50 addresses per request
  • Caching: 24-hour TTL. Cached lookups don’t count against monthly quota.
  • Rate limits: Standard tier limits apply (Growth = 300 req/min)

Next steps

Ready to migrate?

293 tests. 41 routes. USPS-approved. Flat monthly pricing.

3 SDKs (Python · Node · PHP) MIT Licensed No per-label fees