Skip to content
· 12 min read · Tutorial

USPS v3 API with Node.js:
Complete Quickstart Guide

The usps-v3 Node.js SDK gives you a fully typed, zero-dependency interface to the USPS v3 REST API. This tutorial covers installation, authentication, and the six core operations: address validation, package tracking, rate shopping, label creation, error handling, and Express.js integration.

Prerequisites

  • USPS Developer Portal account — Register at developer.usps.com and create an application to get your Client ID and Client Secret.
  • Node.js 18+ — The SDK uses native fetch (no polyfills needed on Node 18+).
  • Zero dependencies — The SDK ships with no external dependencies. Just install and go.
Installation
# Zero dependencies. Works with Node.js 18+.
npm install usps-v3

Authentication

The USPS v3 API uses OAuth 2.0 client credentials grant. Tokens last 8 hours. The SDK handles the entire flow automatically — you never touch a token directly.

Node.js — Client Setup
import { USPSClient } from 'usps-v3';

// Client ID and Client Secret from USPS Developer Portal
// https://developer.usps.com/apis
const client = new USPSClient({
  clientId: 'your_client_id',
  clientSecret: 'your_client_secret',
});

// That's it. OAuth tokens are managed automatically:
//   - Token fetched on first API call
//   - Cached in memory (8-hour lifetime)
//   - Refreshed 30 minutes before expiry
//   - Safe for concurrent requests

Testing environment: Pass environment: 'testing' to the client constructor to use the USPS sandbox at apis-tem.usps.com. Same credentials, no real shipments.

Address Validation

Address validation is the most common API call. It standardizes street addresses, confirms deliverability via DPV (Delivery Point Validation), and flags vacant addresses.

Node.js — Validate an Address
import { USPSClient } from 'usps-v3';

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

// Validate a US address
const result = await client.addresses.validate({
  streetAddress: '1600 Pennsylvania Ave NW',
  city: 'Washington',
  state: 'DC',
  zipCode: '20500',
});

// Access standardized fields
console.log(result.address.streetAddress);   // 1600 PENNSYLVANIA AVE NW
console.log(result.address.city);            // WASHINGTON
console.log(result.address.state);           // DC
console.log(result.address.zipCode);         // 20500
console.log(result.address.zipPlus4);        // 0005

// Delivery Point Validation
console.log(result.address.dpvConfirmation); // Y
console.log(result.address.vacant);          // N
DPV Code Meaning Action
Y Confirmed deliverable Accept the address
S Secondary info missing (apt/suite) Prompt user for unit number
D Secondary confirmed but not matched Verify unit number with user
N Not deliverable Reject or flag for manual review

Package Tracking

Track any USPS package by tracking number. The response includes the current status, delivery date, and a full event history with timestamps and locations.

Node.js — Track a Package
// Track a package by tracking number
const tracking = await client.tracking.get({
  trackingNumber: '9400111899223456789012',
});

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

// Full event history (most recent first)
for (const event of tracking.events) {
  console.log(
    `${event.date} ${event.time}`,
    `| ${event.city}, ${event.state}`,
    `| ${event.description}`,
  );
}

// Output:
// 2026-03-08 10:15 | Washington, DC | Delivered
// 2026-03-08 06:30 | Washington, DC | Out for Delivery
// 2026-03-07 22:10 | Washington, DC | Arrived at Hub

Tip: Tracking data is real-time but changes frequently. Poll at reasonable intervals (every 30-60 minutes) or use USPS webhook notifications for production systems. Don't cache tracking responses.

Rate Shopping

Compare shipping prices across all available USPS services for a given package. The API returns Priority Mail, Priority Mail Express, Ground Advantage, and any other eligible services with prices and estimated delivery times.

Node.js — Compare Shipping Rates
// Compare domestic shipping prices
const rates = await client.prices.search({
  originZip: '10001',        // New York, NY
  destinationZip: '90210',   // Beverly Hills, CA
  weight: 2.5,                 // pounds
  length: 12,                  // inches
  width: 8,
  height: 6,
});

// Iterate over available services
for (const rate of rates.rates) {
  console.log(
    rate.mailClass.padEnd(25),
    `$${rate.totalPrice.toFixed(2).padStart(6)}`,
    `| ${rate.deliveryDays} day(s)`,
  );
}

// Output:
// Priority Mail Express       $32.75 | 1 day(s)
// Priority Mail               $12.10 | 2 day(s)
// USPS Ground Advantage        $8.45 | 5 day(s)

// Get the cheapest option
const cheapest = rates.rates.reduce((min, r) =>
  r.totalPrice < min.totalPrice ? r : min
);
console.log(`Best deal: ${cheapest.mailClass} at $${cheapest.totalPrice.toFixed(2)}`);

Commercial pricing: If you have a USPS commercial account, the SDK automatically returns commercial rates (lower than retail). Set rateIndicator: 'CP' to explicitly request commercial plus pricing.

Label Creation

Create USPS shipping labels programmatically. The API returns a tracking number, a downloadable PDF label, and the final price. Label creation requires a COP (Customer Online Payment) claim linked to your USPS developer account, or a RevAddress Growth plan which handles this for you.

Node.js — Create a Shipping Label
// Create a shipping label
const label = await client.labels.create({
  fromAddress: {
    name: 'Revasser Labs',
    streetAddress: '123 Main St',
    city: 'New York',
    state: 'NY',
    zipCode: '10001',
  },
  toAddress: {
    name: 'Jane Smith',
    streetAddress: '456 Oak Ave',
    city: 'Los Angeles',
    state: 'CA',
    zipCode: '90001',
  },
  mailClass: 'PRIORITY_MAIL',
  weight: 2.5,
  length: 12,
  width: 8,
  height: 6,
});

console.log(label.trackingNumber);  // "9205590100130439839014"
console.log(label.labelUrl);        // URL to download PDF label
console.log(label.totalPrice);      // 12.10

Label formats: Pass labelFormat: 'PDF' (default), 'PNG', or 'ZPL' for thermal printers. The labelUrl is valid for 24 hours.

Error Handling

The SDK throws typed error classes for every error category. The most important one to handle is USPSRateLimitError — USPS caps direct access at 60 requests/hour. The error exposes a retryAfter property (seconds) parsed from the response header.

Node.js — Error Handling with Retry
import { USPSClient } from 'usps-v3';
import {
  USPSError,
  USPSRateLimitError,
  USPSAuthError,
  USPSValidationError,
} from 'usps-v3/errors';

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

async function validateWithRetry(street, city, state, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.addresses.validate({
        streetAddress: street,
        city,
        state,
      });
    } catch (err) {
      if (err instanceof USPSRateLimitError) {
        // 429 — back off using Retry-After header or exponential
        const wait = err.retryAfter ?? 2 ** attempt;
        console.log(`Rate limited. Retrying in ${wait}s...`);
        await new Promise(r => setTimeout(r, wait * 1000));
      } else if (err instanceof USPSAuthError) {
        // 401 — token expired or bad credentials
        throw err;
      } else if (err instanceof USPSValidationError) {
        // Bad input (missing required field, etc.)
        console.error(`Validation error: ${err.message}`);
        throw err;
      } else if (err instanceof USPSError) {
        // Catch-all for any USPS API error
        console.error(`USPS error ${err.statusCode}: ${err.message}`);
        throw err;
      } else {
        throw err;
      }
    }
  }
  throw new USPSRateLimitError('Max retries exceeded');
}
Exception HTTP Status Cause
USPSAuthError 401 Bad credentials or expired token
USPSValidationError 400 Missing or invalid request parameters
USPSRateLimitError 429 Exceeded 60 req/hr limit
USPSError 5xx USPS server error (retry safe)

TypeScript Support

The SDK is written in TypeScript and ships with full type declarations. Every method, parameter, and response field is typed. DPV codes, mail classes, and status categories use string literal unions — your IDE catches typos before runtime.

TypeScript — Full IntelliSense
import { USPSClient } from 'usps-v3';
import type {
  AddressValidationResult,
  TrackingResult,
  RateSearchResult,
  LabelResult,
} from 'usps-v3/types';

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

// Full IntelliSense — every field typed, every method documented
const result: AddressValidationResult = await client.addresses.validate({
  streetAddress: '1600 Pennsylvania Ave NW',
  city: 'Washington',
  state: 'DC',
  zipCode: '20500',
});

// result.address.dpvConfirmation is typed as 'Y' | 'S' | 'D' | 'N'
// result.address.vacant is typed as 'Y' | 'N'
// Hover over any field in your IDE for documentation

No @types/ package needed. Types are bundled with the SDK and work out of the box with tsconfig.json defaults.

Express.js Integration

Drop address validation into any Express.js app as a route. The client is instantiated once at startup — the OAuth token is cached and reused across all requests automatically.

Express.js — Address Validation Route
import express from 'express';
import { USPSClient } from 'usps-v3';

const app = express();
app.use(express.json());

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

app.post('/api/validate-address', async (req, res) => {
  try {
    const result = await usps.addresses.validate({
      streetAddress: req.body.street,
      city: req.body.city,
      state: req.body.state,
      zipCode: req.body.zip,
    });

    res.json({
      valid: result.address.dpvConfirmation === 'Y',
      standardized: result.address,
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000);

Production note: Instantiate the USPSClient once, not per request. The client manages its own token lifecycle and is safe for concurrent use across multiple Express routes.

Next Steps

You've got the fundamentals. Here's where to go from here:

  • npm package — Installation, changelog, and version history.
  • GitHub repo — Source code, issues, and contribution guide. MIT licensed.
  • Full API Documentation — Complete endpoint reference with request/response schemas for all 37 routes.
  • Python Quickstart — Prefer Python? The same walkthrough with the Python SDK.
  • Rate Limit Strategies — Caching, queuing, and architecture patterns for production workloads beyond 60 req/hr.
  • Migration Guide — Moving from USPS Web Tools XML? Complete endpoint mapping and OAuth setup walkthrough.

Need higher rate limits?

RevAddress provides a managed USPS v3 API with 600 req/min, built-in caching, automatic token management, and BYOK support. Drop in the SDK and scale.