Skip to content
· 12 min read · Tutorial

USPS v3 API with PHP:
Complete Quickstart Guide

The revaddress/usps-v3-php Composer package gives you a zero-dependency PHP 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 framework integration for Magento, WooCommerce, and Laravel.

Prerequisites

  • USPS Developer Portal account — Register at developer.usps.com and create an application to get your Client ID and Client Secret.
  • PHP 8.1+ with ext-json and ext-openssl. Compatible with PHP 8.1, 8.2, 8.3, and 8.4.
  • Zero dependencies — No Guzzle, no PSR-7. The SDK uses PHP's built-in file_get_contents with stream contexts.
Installation
# Zero dependencies. PHP 8.1+ with ext-json and ext-openssl.
composer require revaddress/usps-v3-php

Authentication

The USPS v3 API uses OAuth 2.0 client credentials grant. Tokens last 8 hours. The SDK handles the entire flow automatically — tokens are cached to disk so they persist across PHP requests (important for FPM / shared hosting environments).

PHP — Client Setup
use RevAddress\USPSv3\Client;

// Client ID and Client Secret from USPS Developer Portal
// https://developer.usps.com/apis
$usps = new Client('your-client-id', 'your-client-secret');

// That's it. OAuth tokens are managed automatically:
//   - Token fetched on first API call
//   - Cached to disk (8-hour lifetime)
//   - Refreshed 30 minutes before expiry
//   - Custom cache dir: new Client('id', 'secret', cacheDir: '/var/cache/usps')

Token management: Check token status with $usps->tokenStatus() — returns TTL, validity, and payment auth availability. Force a refresh with $usps->refreshTokens(). Tokens are cached to sys_get_temp_dir() by default.

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.

PHP — Validate an Address
use RevAddress\USPSv3\Client;

$usps = new Client('your-client-id', 'your-client-secret');

// Validate a US address
$result = $usps->validateAddress([
    'streetAddress' => '1600 Pennsylvania Ave NW',
    'city'          => 'Washington',
    'state'         => 'DC',
    'ZIPCode'       => '20500',
]);

// Access standardized fields
echo $result['address']['streetAddress'];    // 1600 PENNSYLVANIA AVE NW
echo $result['address']['city'];              // WASHINGTON
echo $result['address']['state'];             // DC
echo $result['address']['ZIPCode'];           // 20500
echo $result['address']['ZIPPlus4'];          // 0005

// Delivery Point Validation
echo $result['address']['DPVConfirmation'];  // Y
echo $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
Migration note: In the retired Web Tools XML API, Address2 was the street and Address1 was the apt/suite — confusingly swapped. The v3 REST API uses streetAddress and secondaryAddress. No more field swaps.

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.

PHP — Track a Package
// Track a package by tracking number
$tracking = $usps->trackPackage('9400111899223456789012');

// Latest status
echo $tracking['trackingNumber'];     // "9400111899223456789012"
echo $tracking['statusCategory'];     // "Delivered"
echo $tracking['status'];               // "Delivered, In/At Mailbox"

// Full event history (most recent first)
foreach ($tracking['trackingEvents'] as $event) {
    printf(
        "%s %s | %s, %s | %s\n",
        $event['eventDate'],
        $event['eventTime'],
        $event['eventCity'],
        $event['eventState'],
        $event['eventDescription']
    );
}

// Output:
// 2026-03-08 10:15 | Washington, DC | Delivered, In/At Mailbox
// 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. Poll at reasonable intervals (every 30-60 minutes) or use USPS webhook notifications. The old <TrackID> XML body is gone — the tracking number is now a path parameter handled automatically by the SDK.

Rate Shopping

Get shipping prices for domestic and international packages. The v3 API requires a mailClass per request (unlike the old RateV4 which returned all classes at once). The SDK validates mail class constants before sending.

PHP — Get Shipping Rates
// Get domestic shipping prices
$rates = $usps->getDomesticPrices([
    'originZIPCode'      => '10001',        // New York, NY
    'destinationZIPCode' => '90210',        // Beverly Hills, CA
    'weight'             => 2.5,            // pounds
    'mailClass'          => 'PRIORITY_MAIL',
    'processingCategory' => 'MACHINABLE',
    'rateIndicator'      => 'DR',           // Dimensional Rectangular
    'priceType'          => 'RETAIL',
]);

// Access rate details
echo $rates['totalBasePrice'];  // "12.10"

// International rates
$intlRates = $usps->getInternationalPrices([
    'originZIPCode'  => '10001',
    'destinationCountryCode' => 'CA',  // ISO alpha-2 (not "Canada")
    'weight'         => 2.5,
    'mailClass'      => 'PRIORITY_MAIL_EXPRESS',
]);
Mail Class Constant Service
PRIORITY_MAIL_EXPRESS 1-2 day guaranteed
PRIORITY_MAIL 1-3 day
USPS_GROUND_ADVANTAGE 2-5 day ground
FIRST-CLASS_PACKAGE_SERVICE 1-5 day (under 1 lb)
PARCEL_SELECT 2-8 day economy
MEDIA_MAIL 2-8 day (books/media only)

Label Creation

Create USPS shipping labels programmatically. The API returns a tracking number and raw PDF label data. Label creation requires BYOK (Bring Your Own Keys) credentials — CRID, MID, and EPA account linked through the USPS Business Customer Gateway.

PHP — Create a Shipping Label
// Create a shipping label (requires BYOK credentials)
$usps = new Client(
    'client-id',
    'client-secret',
    crid:       '56982563',
    masterMid:  '904128936',
    labelMid:   '904128937',
    epaAccount: 'your-eps-account',
);

$label = $usps->createLabel(
    fromAddress: [
        'firstName'     => 'RevAddress',
        'streetAddress' => '228 Park Ave S',
        'city'          => 'New York',
        'state'         => 'NY',
        'ZIPCode'       => '10003',
    ],
    toAddress: [
        'firstName'     => 'Jane',
        'lastName'      => 'Doe',
        'streetAddress' => '1600 Pennsylvania Ave NW',
        'city'          => 'Washington',
        'state'         => 'DC',
        'ZIPCode'       => '20500',
    ],
    mailClass: 'PRIORITY_MAIL',
    weight: 2.5,
);

echo $label['trackingNumber'];
file_put_contents('label.pdf', $label['labelData']);  // PDF bytes

BYOK credentials: Get your CRID, MID, and EPA account from the USPS Business Customer Gateway enrollment guide. Or use a RevAddress Growth plan which handles CRID/MID/COP claims for you.

Error Handling

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

PHP — Error Handling
use RevAddress\USPSv3\Client;
use RevAddress\USPSv3\Exception\USPSException;
use RevAddress\USPSv3\Exception\AuthException;
use RevAddress\USPSv3\Exception\RateLimitException;
use RevAddress\USPSv3\Exception\ValidationException;

$usps = new Client('your-client-id', 'your-client-secret');

try {
    $result = $usps->validateAddress([
        'streetAddress' => '1600 Pennsylvania Ave NW',
        'city'          => 'Washington',
        'state'         => 'DC',
    ]);
} catch (RateLimitException $e) {
    // 429 — wait and retry using Retry-After header
    $retryAfter = $e->getRetryAfter() ?? 60;
    sleep($retryAfter);
} catch (AuthException $e) {
    // 401 — bad credentials or expired token
    $usps->refreshTokens();
} catch (ValidationException $e) {
    // Bad input — check which field failed
    echo $e->getField();  // "streetAddress"
} catch (USPSException $e) {
    // Catch-all — check if retryable (500, 502, 503, 504)
    if ($e->isRetryable()) {
        // Safe to retry
    }
    $body = $e->getResponseBody();  // Raw USPS error
}
Exception HTTP Status Cause
AuthException 401 Bad credentials or expired token
ValidationException 400 Missing or invalid request parameters
RateLimitException 429 Exceeded 60 req/hr limit
USPSException 5xx USPS server error (retry safe via isRetryable())

Magento 2 Integration

If you're fixing the AC-15210 USPS shipping breakage in Magento 2, this SDK is the fastest path. Create a custom carrier module that calls the v3 REST API directly — no XML parsing, no USERID query params, proper OAuth caching.

Magento 2 — Custom Carrier Module
// Magento 2 — Custom carrier module (app/code/RevAddress/Shipping/)
// Model/Carrier/RevAddress.php
namespace RevAddress\Shipping\Model\Carrier;

use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use RevAddress\USPSv3\Client;

class RevAddress extends AbstractCarrier
{
    protected $_code = 'revaddress';
    private Client $usps;

    public function __construct(/* ... */)
    {
        parent::__construct(/* ... */);
        $this->usps = new Client(
            $this->getConfigData('client_id'),
            $this->getConfigData('client_secret'),
        );
    }

    public function collectRates(RateRequest $request)
    {
        $rates = $this->usps->getDomesticPrices([
            'originZIPCode'      => $request->getPostcode(),
            'destinationZIPCode' => $request->getDestPostcode(),
            'weight'             => $request->getPackageWeight(),
            'mailClass'          => 'USPS_GROUND_ADVANTAGE',
            'processingCategory' => 'MACHINABLE',
            'rateIndicator'      => 'DR',
            'priceType'          => 'RETAIL',
        ]);
        // ... build RateResult from $rates
    }
}

See the full Magento AC-15210 fix guide for the complete module structure including etc/config.xml, admin configuration fields, and deployment steps.

Laravel Integration

Register the USPS client as a singleton in a service provider. The client manages its own token lifecycle and is safe for reuse across requests — instantiate once, inject everywhere.

Laravel — Service Provider + Controller
// Laravel — Service provider + singleton
// app/Providers/USPSServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use RevAddress\USPSv3\Client;

class USPSServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->singleton(Client::class, fn() =>
            new Client(
                config('services.usps.client_id'),
                config('services.usps.client_secret'),
                cacheDir: storage_path('framework/cache/usps'),
            )
        );
    }
}

// In any controller or service:
use RevAddress\USPSv3\Client;

class AddressController
{
    public function validate(Request $request, Client $usps)
    {
        $result = $usps->validateAddress([
            'streetAddress' => $request->street,
            'city'          => $request->city,
            'state'         => $request->state,
            'ZIPCode'       => $request->zip,
        ]);

        return response()->json([
            'valid'        => $result['address']['DPVConfirmation'] === 'Y',
            'standardized' => $result['address'],
        ]);
    }
}

Config: Add usps.client_id and usps.client_secret to config/services.php. The cacheDir option points token cache at Laravel's cache directory so tokens persist across FPM workers.

Next Steps

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

  • Packagist — Installation, changelog, version history. 59 tests, 102 assertions.
  • GitHub repo — Source code, issues, CI status (PHP 8.1–8.4). MIT licensed.
  • Full API Documentation — Complete endpoint reference with request/response schemas for all 41 routes.
  • Magento AC-15210 Fix — Complete Magento carrier module with admin config, OAuth caching, and deployment.
  • WooCommerce Migration — Fix broken USPS shipping in WooCommerce with the PHP SDK.
  • Rate Limit Strategies — Caching, queuing, and architecture patterns for production workloads beyond 60 req/hr.

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.

Related Articles